정렬 알고리즘은 다양하므로 특정 시나리오에서 어떤 알고리즘을 사용하는지가 중요합니다. 적합한 알고리즘을 선택하려면 제안된 순서대로 다음 기준을 고려할 수 있습니다.
(1) 실행 시간
(2) 저장 공간
(3) 프로그래밍 노력
작은 경우 데이터 양, (1)과 (2) 사이에는 큰 차이가 없으며, (3)은 데이터 양이 많은 경우 주로 고려됩니다.
1. 버블 정렬
void BubbleSortArray() { for(int i=1;i<n;i++) { for(int j=0;i<n-i;j++) { if(a[j]>a[j+1])//比较交换相邻元素 { int temp; temp=a[j]; a[j]=a[j+1]; a[j+1]=temp; } } } }
효율성 O(n²), 작은 목록 정렬에 적합합니다.
2. 선택 정렬
void SelectSortArray() { int min_index; for(int i=0;i<n-1;i++) { min_index=i; for(int j=i+1;j<n;j++)//每次扫描选择最小项 if(arr[j]<arr[min_index]) min_index=j; if(min_index!=i)//找到最小项交换,即将这一项移到列表中的正确位置 { int temp; temp=arr[i]; arr[i]=arr[min_index]; arr[min_index]=temp; } } }
효율성 O(n²), 작은 목록 정렬에 적합합니다.
3. 삽입 정렬
void InsertSortArray() { for(int i=1;i<n;i++)//循环从第二个数组元素开始,因为arr[0]作为最初已排序部分 { int temp=arr[i];//temp标记为未排序第一个元素 int j=i-1; while (j>=0 && arr[j]>temp)/*将temp与已排序元素从小到大比较,寻找temp应插入的位置*/ { arr[j+1]=arr[j]; j--; } arr[j+1]=temp; } }
효율이 가장 좋은 것은 O(n²)이며 이는 버블 및 선택과 동일합니다. 목록
목록이 기본 Ordered인 경우 삽입 정렬이 버블 및 선택보다 효율적입니다.
void ShellSortArray() { for(int incr=3;incr<0;incr--)//增量递减,以增量3,2,1为例 { for(int L=0;L<(n-1)/incr;L++)//重复分成的每个子列表 { for(int i=L+incr;i<n;i+=incr)//对每个子列表应用插入排序 { int temp=arr[i]; int j=i-incr; while(j>=0&&arr[j]>temp) { arr[j+incr]=arr[j]; j-=incr; } arr[j+incr]=temp; } } }
는 작은 목록을 정렬하는 데 적합합니다.
증분값의 초기 크기에 따라 효율성은 O(nlog2^n)~O(n^1.5)로 추정됩니다. 증분 값이 2의 거듭제곱이면 다음 패스에서 동일한 요소가 다시 비교되므로 증분 값에는 소수를 사용하는 것이 좋습니다.
쉘 정렬은 삽입 정렬을 개선하고 비교 횟수를 줄입니다. 정렬 과정에서 요소가 앞뒤로 이동할 수 있으므로 불안정한 정렬입니다.
void MergeSort(int low,int high) { if(low>=high) return;//每个子列表中剩下一个元素时停止 else int mid=(low+high)/2;/*将列表划分成相等的两个子列表,若有奇数个元素,则在左边子列表大于右侧子列表*/ MergeSort(low,mid);//子列表进一步划分 MergeSort(mid+1,high); int [] B=new int [high-low+1];//新建一个数组,用于存放归并的元素 for(int i=low,j=mid+1,k=low;i<=mid && j<=high;k++)/*两个子列表进行排序归并,直到两个子列表中的一个结束*/ { if (arr[i]<=arr[j];) { B[k]=arr[i]; I++; } else { B[k]=arr[j]; j++; } } for( ;j<=high;j++,k++)//如果第二个子列表中仍然有元素,则追加到新列表 B[k]=arr[j]; for( ;i<=mid;i++,k++)//如果在第一个子列表中仍然有元素,则追加到新列表中 B[k]=arr[i]; for(int z=0;z<high-low+1;z++)//将排序的数组B的 所有元素复制到原始数组arr中 arr[z]=B[z]; }
효율성 O(nlogn), 병합된 최고, 평균, 최악의 경우 효율성 간에 차이가 없습니다.
분할 정복 방법을 기반으로 큰 목록을 정렬하는 데 적합합니다.
/*快速排序的算法思想:选定一个枢纽元素,对待排序序列进行分割,分割之后的序列一个部分小于枢纽元素,一个部分大于枢纽元素,再对这两个分割好的子序列进行上述的过程。*/ void swap(int a,int b){int t;t =a ;a =b ;b =t ;} int Partition(int [] arr,int low,int high) { int pivot=arr[low];//采用子序列的第一个元素作为枢纽元素 while (low < high) { //从后往前栽后半部分中寻找第一个小于枢纽元素的元素 while (low < high && arr[high] >= pivot) { --high; } //将这个比枢纽元素小的元素交换到前半部分 swap(arr[low], arr[high]); //从前往后在前半部分中寻找第一个大于枢纽元素的元素 while (low <high &&arr [low ]<=pivot ) { ++low ; } swap (arr [low ],arr [high ]);//将这个枢纽元素大的元素交换到后半部分 } return low ;//返回枢纽元素所在的位置 } void QuickSort(int [] a,int low,int high) { if (low <high ) { int n=Partition (a ,low ,high ); QuickSort (a ,low ,n ); QuickSort (a ,n +1,high ); } }
평균 효율성은 O(nlogn)로 큰 목록을 정렬하는 데 적합합니다.
이 알고리즘의 총 시간은 피벗 값의 위치에 따라 달라집니다. 첫 번째 요소를 피벗으로 선택하면 최악의 경우 O(n²) 효율성이 발생할 수 있습니다. 기본적으로 숫자가 순서대로 되어 있으면 효율성이 최악이 됩니다. 옵션 중간값을 피벗으로 하면 효율성은 O(nlogn)입니다.
분할 정복 방식을 기반으로 합니다.
7. 힙 정렬
최대 힙: 후자의 비종단 노드의 키워드는 왼쪽 및 오른쪽 자식의 키워드보다 크거나 같습니다. 힙 상단에 있는 노드는 전체 시퀀스에서 가장 크다.
아이디어:
(1) i=l, temp=kl이라고 가정합니다.
(2) i의 왼쪽 자식, j=2i+1을 계산합니다.
(3) j< =n -1이면 (4)로 이동하고, 그렇지 않으면 (6)으로 이동합니다.
(4) kj와 kj+1을 비교하고, kj+1>kj이면 j=j+1로 두고, 그렇지 않으면 j는 그대로 유지됩니다. 변경되지 않음
( 5) temp와 kj를 비교합니다. kj>temp이면 ki를 kj와 같게 하고 i=j, j=2i+1로 두고 (3)으로 이동하고, 그렇지 않으면 (6)으로 이동합니다. 🎜> (6) 기를 같게 하여 온도, 끝을 맺습니다.
void HeapSort(SeqIAst R) { //对R[1..n]进行堆排序,不妨用R[0]做暂存单元 int I; BuildHeap(R); //将R[1-n]建成初始堆for(i=n;i>1;i--) //对当前无序区R[1..i]进行堆排序,共做n-1趟。 { R[0]=R[1]; R[1]=R[i]; R[i]=R[0]; //将堆顶和堆中最后一个记录交换 Heapify(R,1,i-1); //将R[1..i-1]重新调整为堆,仅有R[1]可能违反堆性质 } }
힙 정렬의 최악의 시간 복잡도는 O(nlgn)입니다. 힙 정렬의 평균 성능은 최악의 성능에 가깝습니다. 초기 힙을 구축하기 위해서는 많은 수의 비교가 필요하기 때문에 힙 정렬은 레코드 수가 적은 파일에는 적합하지 않습니다. 힙 정렬은 내부 정렬이며 보조 공간은 O(1)입니다.
힙 정렬과 직접 삽입 정렬의 차이점:
직접 선택 정렬에서는 R[1..n]에서 가장 작은 키워드를 가진 레코드를 선택하기 위해 n-1 비교를 해야 한다. , R[2..n]에서 가장 작은 키워드가 있는 레코드를 선택하려면 n-2 비교가 필요합니다. 실제로 후속 n-2 비교에서는 이전 n-1 비교에서 많은 비교가 수행되었을 수 있지만 이러한 비교 결과는 이전 정렬 단계에서 유지되지 않았기 때문에 이후 정렬 단계에서 반복되었습니다. 작업이 수행됩니다.
힙 정렬은 일부 비교 결과를 트리 구조로 저장하여 비교 횟수를 줄일 수 있습니다.
8. 위상 정렬
예: 학생 선택 과목의 편성 순서
위상 정렬: 유방향 그래프의 꼭지점을 상호 우선순위 관계에 따라 선형 순서로 정렬합니다. 시퀀스 프로세스.
방법:
유향 그래프에서 선행자가 없는 정점을 선택하여 출력합니다.
그래프에서 정점과 정점으로 끝나는 모든 호를 삭제합니다.
모든 정점이 출력될 때까지 위의 두 단계를 반복합니다. (위상 정렬이 성공함) 또는 그래프에 선행자가 없는 정점이 없을 때까지(그래프에 주기가 있음).
void TopologicalSort()/*输出拓扑排序函数。若G无回路,则输出G的顶点的一个拓扑序列并返回OK,否则返回ERROR*/ { int indegree[M]; int i,k,j; char n; int count=0; Stack thestack; FindInDegree(G,indegree);//对各顶点求入度indegree[0....num] InitStack(thestack);//初始化栈 for(i=0;i<G.num;i++) Console.WriteLine("结点"+G.vertices[i].data+"的入度为"+indegree[i]); for(i=0;i<G.num;i++) { if(indegree[i]==0) Push(thestack.vertices[i]); } Console.Write("拓扑排序输出顺序为:"); while(thestack.Peek()!=null) { Pop(thestack.Peek()); j=locatevex(G,n); if (j==-2) { Console.WriteLine("发生错误,程序结束。"); exit(); } Console.Write(G.vertices[j].data); count++; for(p=G.vertices[j].firstarc;p!=NULL;p=p.nextarc) { k=p.adjvex; if (!(--indegree[k])) Push(G.vertices[k]); } } if (count<G.num) Cosole.WriteLine("该图有环,出现错误,无法排序。"); else Console.WriteLine("排序成功。"); }
9. 챔피언십 정렬
챔피언십 정렬의 알고리즘 아이디어는 스포츠 경기와 유사합니다.
먼저 n개의 데이터 요소를 쌍으로 그룹화하고 키워드별로 비교하여 n/2개의 비교 우승자(더 작은 키워드를 가진 우승자)를 얻고, 이는 첫 번째 비교 단계의 결과로 유지됩니다.
그런 다음 이 n/ 그런 다음 2개의 데이터 요소를 두 그룹으로 그룹화하고 키워드에 따라 비교하는 방식으로 가장 작은 키워드가 있는 데이터 요소가 선택될 때까지 계속됩니다.
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <math.h> #define SIZE 100000 #define MAX 1000000 struct node { long num;//关键字 char str[10]; int lastwin;//最后胜的对手 int killer;//被击败的对手 long times;//比赛次数 }data[SIZE]; long CompareNum=0; long ExchangeNum=0; long Read(char name[])//读取文件a.txt中的数据,并存放在数组data[]中;最后返回数据的个数 { FILE *fp; long i=1; fp=fopen(name,"rw"); fscanf(fp,"%d%s",&data[i].num,data[i].str); while(!feof(fp)) { i++; fscanf(fp,"%d%s",&data[i].num,data[i].str); } return (i-1); } long Create(long num)//创建胜者树,返回冠军(最小数)在数组data[]中的下标 { int i,j1,j2,max,time=1; long min;//记录当前冠军的下标 for(i=1;pow(2,i-1)<num;i++) max=pow(2,i-1);//求叶子结点数目 for(i=1;i<=max;i++)//初始化叶子结点 { data[i].killer=0; data[i].lastwin=0; data[i].times=0; if(i>num) data[i].num=MAX; } for(i=1;i<=max;i+=2)//第一轮比赛 { ++CompareNum; if(data[i].num <= data[i+1].num) { data[i].lastwin = i+1; data[i+1].killer=i; ++data[i].times; ++data[i+1].times; min=i; } else { data[i+1].lastwin=i; data[i].killer=i+1; ++data[i].times; ++data[i+1].times; min=i+1; } } j1=j2=0;//记录连续的两个未被淘汰的选手的下标 while(time <= (log(max)/log(2)))//进行淘汰赛 { for(i=1;i<=max;i++) { if(data[i].times==time && data[i].killer==0)//找到一名选手 { j2=i;//默认其为两选手中的后来的 if(j1==0)//如果第一位置是空的,则刚来的选手先来的 j1=j2; else//否则刚来的选手是后来的,那么选手都已到场比赛开始 { ++CompareNum; if(data[j1].num <= data[j2].num)//先来的选手获胜 { data[j1].lastwin = j2;//最后赢的是j2 data[j2].killer=j1;//j2是被j1淘汰的 ++data[j1].times; ++data[j2].times;//两选手场次均加1 min=j1;//最小数下标为j1 j1=j2=0;//将j1,j2置0 } else//同理 { data[j2].lastwin=j1; data[j1].killer=j2; ++data[j1].times; ++data[j2].times; min=j2; j1=j2=0; } } } } time++;//轮数加1 } return min;//返回冠军的下标 } void TournamentSort(long num)//锦标赛排序 { long tag=Create(num);//返回最小数下标 FILE *fp1; fp1=fopen("sort.txt","w+");//为写入创建并打开文件sort.txt while(data[tag].num != MAX)//当最小值不是无穷大时 { printf("%d %s\n",data[tag].num,data[tag].str);//输出数据 fprintf(fp1,"%d %s\n",data[tag].num,data[tag].str);//写入数据 data[tag].num=MAX;//将当前冠军用无穷大替换 tag=Create(num);//返回下一个冠军的下标 } } int main() { int num; char name[10]; printf("Input name of the file:"); gets(name); num=Read(name);//读文件 TournamentSort(num);//锦标赛排序 printf("CompareNum=%d\nExchangeNum=%d\n",CompareNum,ExchangeNum); return 0; }
十、基数排序
基数排序又被称为桶排序。与前面介绍的几种排序方法相比较,基数排序和它们有明显的不同。
前面所介绍的排序方法都是建立在对数据元素关键字进行比较的基础上,所以可以称为基于比较的排序;
而基数排序首先将待排序数据元素依次“分配”到不同的桶里,然后再把各桶中的数据元素“收集”到一起。
通过使用对多关键字进行排序的这种“分配”和“收集”的方法,基数排序实现了对多关键字进行排序。
例:
每张扑克牌有两个“关键字”:花色和面值。其大小顺序为:
花色:§<¨<©<ª
面值:2<3<……<K<A
扑克牌的大小先根据花色比较,花色大的牌比花色小的牌大;花色一样的牌再根据面值比较大小。所以,将扑克牌按从小到大的次序排列,可得到以下序列:
§2,…,§A,¨2,…,¨A,©2,…,©A,ª2,…,ªA
这种排序相当于有两个关键字的排序,一般有两种方法实现。
其一:可以先按花色分成四堆(每一堆牌具有相同的花色),然后在每一堆牌里再按面值从小到大的次序排序,最后把已排好序的四堆牌按花色从小到大次序叠放在一起就得到排序的结果。
其二:可以先按面值排序分成十三堆(每一堆牌具有相同的面值),然后将这十三堆牌按面值从小到大的顺序叠放在一起,再把整副牌按顺序根据花色再分成四堆(每一堆牌已按面值从小到大的顺序有序),最后将这四堆牌按花色从小到大合在一起就得到排序的结果。
实现方法:
最高位优先(Most Significant Digit first)法,简称MSD法:先按k1排序分组,同一组中记录,关键码k1相等,再对各组按k2排序分成子组,之后,对后面的关键码继续这样的排序分组,直到按最次位关键码kd对各子组排序后。再将各组连接起来,便得到一个有序序列。
最低位优先(Least Significant Digit first)法,简称LSD法:先从kd开始排序,再对kd-1进行排序,依次重复,直到对k1排序后便得到一个有序序列。
using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace LearnSort { class Program { static void Main(string[] args) { int[] arr = CreateRandomArray(10);//产生随机数组 Print(arr);//输出数组 RadixSort(ref arr);//排序 Print(arr);//输出排序后的结果 Console.ReadKey(); } public static void RadixSort(ref int[] arr) { int iMaxLength = GetMaxLength(arr); RadixSort(ref arr, iMaxLength); } private static void RadixSort(ref int[] arr, int iMaxLength) { List<int> list = new List<int>();//存放每次排序后的元素 List<int>[] listArr = new List<int>[10];//十个桶 char currnetChar;//存放当前的字符比如说某个元素123 中的2 string currentItem;//存放当前的元素比如说某个元素123 for (int i = 0; i < listArr.Length; i++)//给十个桶分配内存初始化。 listArr[i] = new List<int>(); for (int i = 0; i < iMaxLength; i++)//一共执行iMaxLength次,iMaxLength是元素的最大位数。 { foreach (int number in arr)//分桶 { currentItem = number.ToString();//将当前元素转化成字符串 try { currnetChar = currentItem[currentItem.Length-i-1]; }//从个位向高位开始分桶 catch { listArr[0].Add(number); continue; }//如果发生异常,则将该数压入listArr[0]。比如说5 是没有十位数的,执行上面的操作肯定会发生越界异常的,这正是期望的行为,我们认为5的十位数是0,所以将它压入listArr[0]的桶里。 switch (currnetChar)//通过currnetChar的值,确定它压人哪个桶中。 { case '0': listArr[0].Add(number); break; case '1': listArr[1].Add(number); break; case '2': listArr[2].Add(number); break; case '3': listArr[3].Add(number); break; case '4': listArr[4].Add(number); break; case '5': listArr[5].Add(number); break; case '6': listArr[6].Add(number); break; case '7': listArr[7].Add(number); break; case '8': listArr[8].Add(number); break; case '9': listArr[9].Add(number); break; default: throw new Exception("unknow error"); } } for (int j = 0; j < listArr.Length; j++)//将十个桶里的数据重新排列,压入list foreach (int number in listArr[j].ToArray<int>()) { list.Add(number); listArr[j].Clear();//清空每个桶 } arr = list.ToArray<int>();//arr指向重新排列的元素 //Console.Write("{0} times:",i); Print(arr);//输出一次排列的结果 list.Clear();//清空list } } //得到最大元素的位数 private static int GetMaxLength(int[] arr) { int iMaxNumber = Int32.MinValue; foreach (int i in arr)//遍历得到最大值 { if (i > iMaxNumber) iMaxNumber = i; } return iMaxNumber.ToString().Length;//这样获得最大元素的位数是不是有点投机取巧了... } //输出数组元素 public static void Print(int[] arr) { foreach (int i in arr) System.Console.Write(i.ToString()+'\t'); System.Console.WriteLine(); } //产生随机数组。随机数的范围是0到1000。参数iLength指产生多少个随机数 public static int[] CreateRandomArray(int iLength) { int[] arr = new int[iLength]; Random random = new Random(); for (int i = 0; i < iLength; i++) arr[i] = random.Next(0,1001); return arr; } } }
基数排序法是属于稳定性的排序,其时间复杂度为O (nlog(r)m),其中r为所采取的基数,而m为堆数,在某些时候,基数排序法的效率高于其它的比较性排序法。
更多十种JAVA排序算法实例相关文章请关注PHP中文网!