【数据结构之内部排序】插入排序、快速排序、选择排序、归并排序
排序方法分为两大类:一类是内部排序,指的是待排序记录存放在计算机随机存储器中进行的排序过程;另一类是外部排序,指的是待排序记录的数量很大,以致内容一次不能容纳全部记录,在排序中尚需对外存进行访问的排序过程。
内部排序按照排序过程所需的工作量来区别的话,可分为三类:(1)简单的排序方法,其时间复杂度为O(n^2); (2)先进的排序方法,其时间复杂度为O(nlogn); (3)基数排序,其时间复杂度为O(d*n);
这里主要就三类每一类介绍一两个经典的算法,其中均采用顺序存储结构进行操作。
1.简单排序
(1)直接插入排序(Straight Insertion Sort)
直接插入排序的基本操作是从一个有序的表中插入一个新元素,从而得到一个新的有序表。
算法如下:
1 void insertsort(int *array, int num) 2 { 3 int temp, i, j; 4 for (i = 1; i < num; i++) 5 { 6 if (array[i] < array[i - 1]) //当插入元素比前面元素小 7 { 8 temp = array[i]; 9 for (j = i - 1; j >= 0 && array[j] > temp; j--) //将前i个比插入元素小的元素全部往后挪一位,然后插入待插入元素 10 array[j + 1] = array[j]; 11 12 array[j + 1] = temp; 13 } 14 } 15 }
(2)折半插入排序(Binary Insertion Sort)
直接插入排序适合于当待排序记录的数量n很小时使用,但是当n很大时,则不宜使用直接插入,此时可以使用折半插入排序。
算法思想:
将一个元素插入有序表中,找到表头元素和表尾元素,设m为中间元素,比较中间元素m与待插入元素,若m大,则在m到表尾元素中寻找插入位置,否则在表头到m中寻找插入位置,重复该过程,知道最终行成新有序表。
算法如下:
1 void BinaryInsertSort(int *a, int n) 2 { 3 int i, k, low, high, temp, m ; 4 for(i = 1; i < n; i++) 5 { 6 low = 0; 7 high = i - 1; 8 9 while(low <= high) 10 { 11 m = (low + high) / 2; 12 if(a[m] > a[i]) high = m - 1; 13 else low = m + 1; 14 } 15 int temp = a[i]; 16 for(k = i; k > m; k--) 17 a[k] = a[k-1]; 18 a[high + 1] = temp; 19 } 20 }
总结:折半插入排序所需附加存储空间和直接插入排序相同,从时间上比较,折半插入仅仅减少关键字间的比较次数,而记录的次数不表,因此,两种算法的时间复杂度都是O(n^2)。
2.快速排序
(1)冒泡排序(Bubble Sort)
冒泡排序过程很简单,从第一个元素开始,每次和后面的元素逐个比较,如果比后面元素大则交换位置,每趟冒泡把一个最大的元素放到最后,最后形成新的有序表。
算法如下:
1 void BubbleSort(int *a,int n) 2 { 3 int i,j,temp; 4 for (i=0;i<n;++i) 5 for (j=0;j<n-i-1;++j) 6 { 7 if (a[j]>a[j+1]) 8 { 9 temp=a[j]; 10 a[j]=a[j+1]; 11 a[j+1]=temp; 12 } 13 } 14 }
(2)快速排序(Quick Sort)
快速排序是对冒泡排序的一种改进。
算法思路:通过一躺排序将待排记录分割成独立的两部分,其中一部分记录的关键字均比另外一部分的关键字要小,然后继续分别对这两部分重复该步骤,最后达到整个序列有序。
步骤:1.首先找到一个记录作为枢轴(pivot);
2.以该枢轴将序列分割为两部分;
3.以此类推;
关于找枢轴的方法,主要有一下三种:
1.固定位置:取序列的最后一个元素或第一个元素;
2.随机选取;
3.三数取中:使用左端、中间、右端三个中的中值作为枢轴。
这里举了选取最后一个元素作为枢轴的算法:
1 int partion(int *a,int low,int high) 2 { 3 int x=a[high]; 4 int temp; 5 int j,i=low-1; 6 for (j=low;j<high;j++) 7 { 8 if (a[j]<x) //如果元素比最后一个小,则交换 9 { 10 i++; 11 temp=a[i]; 12 a[i]=a[j]; 13 a[j]=temp; 14 } 15 } 16 temp=a[i+1]; 17 a[i+1]=a[high]; 18 a[high]=temp; 19 20 return i+1; 21 } 22 23 void Qsort(int *a,int low,int high) 24 { 25 if (low<high) 26 { 27 int pivotloc=partion(a,low,high); //每次利用递归将每个小部分划分,最终达到有序 28 Qsort(a,low,pivotloc-1); 29 Qsort(a,pivotloc+1,high); 30 } 31 }
具体的分析如下:
e.g:给出5个数 2 7 8 1 5 6 4
第一趟划分后变成 2 1 4 7 5 6 8
然后利用递归,对4左边的序列2 1划分变成1 2,右边部分变成5 6 7 8,最后合并有序。
总结:冒泡排序的时间复杂度为O(n^2),而快速排序最佳时间复杂度为O(n*logn),最坏为O(n^2)。
3.选择排序
(1)简单选择排序(Seletion Sort)
算法思想:通过n-i次关键字间的比较,每一趟在n-i-1个记录中选取关键字最小的记录作为有序序列中的第i个记录,并和第i个记录交换值。
算法如下:
1 void select_sort(int a[],int n)//n为数组a的元素个数 2 { 3 //进行N-1轮选择 4 for(int i=0; i<n-1; i++) 5 { 6 int min_index = i; 7 //找出第i小的数所在的位置 8 for(int j=i+1; j<n; j++) 9 { 10 if(a[j] < a[min_index]) 11 { 12 min_index = j; 13 } 14 } 15 //将第i小的数,放在第i个位置;如果刚好,就不用交换 16 if( i != min_index) 17 { 18 int temp = a[i]; 19 a[i] = a[min_index]; 20 a[min_index] = temp; 21 } 22 } 23 }
4.归并排序
归并排序(Merging Sort)是将两个或两个以上的有序表组合成一个新的有序表。
算法思路:假设初始序列有n个记录,则可看成是n个有序的子序列,每个子序列的长度为1,然后两两合并,得到[n/2]个长度为2或1的有序子序列;再两两归并,如此重复最终活的一个长度为n的有序序列。
算法如下:
1 void Merge(int arr[],int low,int mid,int high) 2 { 3 int i,k; 4 int *temp=(int *)malloc((high-low-1)*sizeof(int)); 5 int left_low=low; 6 int left_high=mid; 7 int right_low=mid+1; 8 int right_high=high; 9 10 for (k=0;left_low<left_high&&right_low<right_high;++k) 11 { 12 if (arr[left_low]<arr[right_low]) //从左右两个序列中挑选出最小的元素放在temp数组中 13 temp[k]=arr[left_low++]; 14 else 15 temp[k]=arr[right_low++]; 16 } 17 18 //将挑选后剩余的元素放进temp数组 19 while (left_low<left_high) 20 temp[k++]=arr[left_low++]; 21 while (right_low<right_high) 22 temp[k++]=arr[right_low++]; 23 24 for (i=0;i<high-low-1;++i) //将有序的temp数组复制到arr数组中 25 arr[low+i]=temp[i]; 26 27 free(temp); 28 } 29 30 void MergeSort(int arr[],int first,int last) 31 { 32 int m=0; 33 if (first<last) 34 { 35 m=(first+last)/2; 36 MergeSort(arr,first,m); //将左序列继续划分为小部分 37 MergeSort(arr,m+1,last); //将右序列继续划分为小部分 38 Merge(arr,first,m,last); 39 } 40 }
总结:归并排序的时间复杂度为O(n*logn)。
各种内部排序算法的比较: