原地址:http://blog.chinaunix.net/uid-22299725-id-2436722.html
1.排序概述
2.内部排序
2.1 插入排序(直接插入排序)
有一个已经有序的数据序列,要求在这个已经排好的数据序列中插入一个数,但要求插入后此数据序列仍然有序,这个时候就要用到一种新的排序方法——插入排序,插入排序的基本操作就是将一个数据插入到已经排好序的有序数据中,从而得到一个新的、个数加一的有序数据,算法适用于少量数据的排序,时间复杂度为O(n^2)。是稳定的排序方法
插入排序示例:
语法:void Insort(double *a,int n); |
|
参数: |
|
double *a: |
待排序数组 |
n: |
数组元素个数 |
返回值: |
无 |
注意: |
数组元素从0开始存储 |
|
本例排序为升序排序 |
源程序: |
|
|
void Insort(double *a,int n) { int i,j; double x; for(i=0; i<n; i++) { x=a[i]; j=i-1; while(j>=0&&a[j]>x) { a[j+1]=a[j]; j--; } a[j+1]=x; } } |
2.2 快速排序
快速排序(Quicksort)是对冒泡排序的一种改进。由C. A. R. Hoare在1962年提出。它的基本思想是:通过一趟排序将要排序的数据分割成独立的两部分,其中一部分的所有数据都比另外一部分的所有数据都要小,然后再按此方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行,以此达到整个数据变成有序序列。
语法:void QuickSort(double *a,int p,int r); |
||
参数: |
||
double *a: |
待排序数组 |
|
int p: |
数组元素的起始位置 |
|
int r: |
数组元素的结束位置 |
|
返回值: |
无 |
|
语法:int Partition(double *a,int p,int r); |
||
参数: |
||
double *a: |
待分割数组 |
|
int p: |
子数组array[p..q] 起始位置 |
|
int r: |
子数组array[p..q] 结束位置 |
|
返回值: |
分割点在数组中的位置 |
|
注意: |
本例排序为升序排序 |
|
源程序: |
|
|
|
void Exchange(double *a, double *b)//交换两个数 { double c; c=*a; *a=*b; *b=c; }
int Partition(double *a,int p,int r) { double x; int j,i; x = a[r]; i = p - 1; for (j = p;j <=r-1 ;j++) { if(a[j] <= x)//升序,降序:if(a[j] >= x) { i++; Exchange(a+i,a+j); } } Exchange(a+i+1,a+r); return i+1; } int Randomized_partition(double *a,int p,int r) { int i; //产生p--r范围内的随机数 i = p + rand()%(r-p+1); Exchange(a+r,a+i); return Partition(a,p,r);
} void QuickSort(double *a,int p,int r) { int q; if (p<r) { q = Partition(a,p,r); QuickSort(a,p,q-1); QuickSort(a,q+1,r); } } |
|
2.3 选择排序(简单选择排序)
每一趟从待排序的数据元素中选出最小(或最大)的一个元素,顺序放在已排好序的数列的最后,直到全部待排序的数据元素排完。 选择排序是不稳定的排序方法。
【示例】:
初始关键字 [49 38 65 97 76 13 27 49]
第一趟排序后 13 [38 65 97 76 49 27 49]
第二趟排序后 13 27 [65 97 76 49 38 49]
第三趟排序后 13 27 38 [97 76 49 65 49]
第四趟排序后 13 27 38 49 [76 97 65 49 ]
第五趟排序后 13 27 38 49 49 [97 65 76]
第六趟排序后 13 27 38 49 49 65 [97 76]
第七趟排序后 13 27 38 49 49 65 76 [97]
最后排序结果 13 27 38 49 49 65 76 97
语法:void select_sort(double array[], int n) ; |
|
参数: |
|
double *array: |
待排序数组 |
n: |
数组元素个数 |
返回值: |
无 |
注意: |
数组元素从0开始存储 |
|
本例排序为升序排序 |
源程序: |
|
|
void select_sort(double array[], int n) { int i,j; int minj; double temp;
for(i = 0; i < n; i++ ) { minj = i; for(j = i+1; j < n; j++ ) { if(array[minj] > array[j]) { minj = j; } } if(minj != i) { temp = array[i]; array[i] = array[minj]; array[minj] = temp; } }
} |
2.4 归并排序
归并(Merge)排序法是将两个(或两个以上)有序表合并成一个新的有序表,即把待排序序列分为若干个子序列,每个子序列是有序的。然后再把有序子序列合并为整体有序序列。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为2-路归并。
归并排序具体工作原理如下(假设序列共有n个元素):
(1)将序列每相邻两个数字进行归并操作(merge),形成floor(n/2)个序列,排序后每个序列包含两个元素
(2)将上述序列再次归并,形成floor(n/4)个序列,每个序列包含四个元素
(3)重复步骤2,直到所有元素排序完毕
语法:void MergeSort(int array[], int first, int last); |
||
参数: |
||
int *array: |
待排序数组 |
|
int first: |
数组元素的起始位置 |
|
int last: |
数组元素的结束位置 |
|
返回值: |
无 |
|
语法:void Merge(int array[], int p, int q, int r); |
||
参数: |
||
int *array: |
待排序数组 |
|
int p: |
子数组array[p..q] 起始位置 |
|
int q: |
子数组array[p..q] 结束位置 |
|
int r: |
子数组array[q+1..r]结束位置 |
|
注意: |
本例排序为升序排序 |
|
源程序: |
|
|
|
void MergeSort(int array[], int first, int last) { int mid = 0; if(first<last) { mid = (first+last)/2; MergeSort(array, first, mid); MergeSort(array, mid+1,last); Merge(array,first,mid,last); } } /** * 0 <= p <= q < r, subarray array[p..q] and array[q+1..r] are already sorted. * the merge() function merges the two sub-arrays into one sorted array. */ void Merge(int array[], int p, int q, int r) { int i,k; int begin1,end1,begin2,end2; int* temp = (int*)malloc((r-p+1)*sizeof(int)); begin1= p; end1 = q; begin2 = q+1; end2 = r; k = 0; while((begin1 <= end1)&&( begin2 <= end2)) { if(array[begin1] < array[begin2]) { temp[k] = array[begin1]; begin1++; } else { temp[k] = array[begin2]; begin2++; } k++; } while(begin1<=end1) { temp[k++] = array[begin1++]; } while(begin2<=end2) { temp[k++] = array[begin2++]; } for (i = 0; i < (r - p + 1); i++) array[p+i] = temp[i]; free(temp); } |
2.4.1 原地归并算法
语法:void sort(int v[],int n) ; |
|
参数: |
|
int *v: |
待排序数组 |
int n: |
数组元素个数 |
注意: |
本例排序为升序排序 |
数组元素从0开始存储 |
|
源程序: |
|
|
void sort(int v[],int n) { int gap,i,j,temp; for(gap= n/2;gap > 0;gap /=2) for(i=gap;i <n;i++) for(j=i-gap;j>=0 && v[j]>v[j+gap];j-=gap) { temp = v[j]; v[j] = v[j+gap]; v[j+gap] = temp; } } |
2.5 冒泡排序
依次比较相邻的两个数,将小数放在前面,大数放在后面。即在第一趟:首先比较第1个和第2个数,将小数放前,大数放后。然后比较第2个数和第3个数,将小数放前,大数放后,如此继续,直至比较最后两个数,将小数放前,大数放后。至此第一趟结束,将最大的数放到了最后。在第二趟:仍从第一对数开始比较(因为可能由于第2个数和第3个数的交换,使得第1个数不再小于第2个数),将小数放前,大数放后,一直比较到倒数第二个数(倒数第一的位置上已经是最大的),第二趟结束,在倒数第二的位置上得到一个新的最大数(其实在整个数列中是第二大的数)。如此下去,重复以上过程,直至最终完成排序。
语法:void Bubsort(double *a,int n); |
|
参数: |
|
double *a: |
待排序数组 |
n: |
数组元素个数 |
返回值: |
无 |
注意: |
数组元素从0开始存储 |
|
本例排序为升序排序 |
本例为改进版冒泡排序,最原始的冒泡排序请去掉关于flag的相关语言,这里利用flag标志,当在冒泡过程中flag的值不再发生变化表明已经排好序。 |
|
源程序: |
|
|
void Bubsort(double *a,int n) { int i,j,flag; double t; for(i=0; i<n-1; i++) { flag=1; for(j=0; j<n-i; j++) if(a[j]>a[j+1]) { t=a[j]; a[j]=a[j+1]; a[j+1]=t; flag=0; } if(flag) break; }
} |
2.6 希尔排序
希尔排序(缩小增量法) 属于插入类排序,是将整个无序列分割成若干小的子序列分别进行插入排序。
排序过程:先取一个正整数d1<n,把所有序号相隔d1的数组元素放一组,组内进行直接插入排序;然后取d2<d1,重复上述分组和排序操作;直至di=1,即所有记录放进一个组中排序为止 ,注意最后一个增量一定是1.
示例:
初始:d=5
49 38 65 97 76 13 27 49* 55 04
|-------------------------|
49 13 排序为13 49
|------------------------|
38 27 排序为27 38
|-------------------------|
65 49* 排序为49* 65
|-------------------------|
97 55 排序为55 97
|--------------------------|
76 04 排序为04 76
一趟结果: 13 27 49* 55 04 49 38 65 97 76
d=3
13 27 49* 55 04 49 38 65 97 76
|---------------|--------------|--------------| 排序为13 38 55 76
13 55 38 76
|----------------|--------------| 排序为04 27 65
27 04 65
|--------------|--------------| 排序为49 49 97
49* 49 97
二趟结果:13 04 49* 38 27 49 66 65 97 76
d=1
三趟结果:04 13 27 38 49* 49 55 65 76 97
语法:void shellsort(double *array,int n); |
|
参数: |
|
double *array: |
待排序数组 |
n: |
数组元素个数 |
返回值: |
无 |
注意: |
数组元素从0开始存储 |
|
本例排序为升序排序 |
函数作用: |
排序,升序 |
源程序: |
|
|
void shellsort(double *array,int n) { int i,j,dk;//dk为序列增量 double temp; int k; dk = n/2; while(dk != 0) {
for(i = dk; i < n; i++) {
temp = array[i]; j = i-dk; while(j >= 0) { k = j+dk; if(array[j] <= array[k]) { j = -1; } else { temp=array[j]; array[j]=array[k]; array[k]=temp;
} j=j-dk; } } dk=dk/2; } } |
2.7 堆排序
堆排序(Heapsort)是指利用堆积树(堆)这种资料结构所设计的一种排序算法,可以利用数组的特点快速定位指定索引的元素。
将序列所存储的向量R[1..n]看做是一棵完全二叉树的存储结构,则堆实质上是满足如下性质的完全二叉树:
树中任一非叶结点的关键字均不大于(或不小于)其左右孩子(若存在)结点的关键字。 【例】关键字序列(10,15,56,25,30,70)和(70,56,30,25,15,10)分别满足堆性质(1)和(2),故它们均是堆,其对应的完全二叉树分别如小根堆示例和大根堆示例所示。
堆的非叶子节点
若数组中元素个数为n,则二叉树的非叶子节点为及其之前的节点,之后的节点全部为叶子节点,我们在建立大根堆性质的时候只需要逆序调整从到第一个节点为大根堆性质。
堆的高度
堆可以被看成是一棵树,结点在堆中的高度可以被定义为从本结点到叶子结点的最长简单下降路径上边的数目;定义堆的高度为树根的高度。我们将看到,堆结构上的一些基本操作的运行时间至多是与树的高度成正比,为O(lgn),其节点性质如下:
PARENT(i)
LEFT(i)
return 2i
RIGHT(i)
return 2i + 1
堆排序示例:
如上图所示(a)图为第一次建立大根堆的情景,假设一个有n个元素,图(b)说明了第一次选择出当前堆中的最大元素跟最后一位相互调换,然后大根堆的性质遭到破坏,调用重建堆性质函数重新建立大根堆,此时堆的大小已经为n-1了,如此循环下去直到堆中剩一个一个元素为止,至此堆排序已经完成。
语法:void Max_heapify(double *a,int i,int Heapsize); |
||
参数: |
||
double *a: |
待排序数组 |
|
int i: |
子堆i为子堆的根节点 |
|
int Heapsize: |
堆大小 |
|
返回值: |
无 |
|
函数作用: |
使i为根节点的堆满足大根堆性质,每个堆以及子堆的都是相应位置的最大值 |
|
语法:void Bulid_max_heap(double *a ,int Heapsize , int length); |
||
参数: |
||
double *a: |
待排序数组 |
|
int Heapsize : |
堆大小 |
|
int length: |
数组长度 |
|
函数作用: |
将待排序的数组建立为大根堆 |
|
语法:void HeapSort(double *a ,int Heapsize,int length); |
||
参数: |
||
double *a: |
待排序数组 |
|
int Heapsize: |
堆大小 |
|
int length: |
数组长度 |
|
函数作用: |
堆排序,升序 |
|
注意: |
数组元素从1开始存储 |
|
源程序: |
|
|
|
void exchange(double *a, double *b) { double t; t = *a; *a = *b; *b = t;
} int Parent(int i)//求i节点的父节点 { return i/2; } int Left(int i)//求i节点的左孩子节点 { return 2*i; } int Right(int i)//求i节点的右孩子节点 { return 2*i+1; } void Max_heapify(double *a,int i,int Heapsize) { int largest,l,r; l = Left(i); r = Right(i); if (l <= Heapsize && a[l] > a[i] ) { largest = l; } else { largest = i; } if ( r <= Heapsize && a[r] > a[largest]) { largest = r; } if (largest != i) { exchange(&a[i],&a[largest]); Max_heapify(a,largest,Heapsize); }
} void Bulid_max_heap(double *a ,int Heapsize , int length) { int i; Heapsize = length; i=length/2; for (;i>=1;i--) { Max_heapify(a,i,Heapsize); } } void HeapSort(double *a ,int Heapsize,int length) { int i; Bulid_max_heap(a , Heapsize , length); for (i = length ;i>=2 ; i--) { exchange(&a[1],&a[i]); Heapsize = Heapsize -1; Max_heapify(a,1,Heapsize); }
} |
3.各种排序性能比较
按平均时间将排序分为四类:
(1)平方阶(O(n2))排序
一般称为简单排序,例如直接插入、直接选择和冒泡排序;
(2)线性对数阶(O(nlgn))排序
如快速、堆和归并排序;
(3)O(n1+£)阶排序
£是介于0和1之间的常数,即0<£<1,如希尔排序;
(4)线性阶(O(n))排序
如桶、箱和基数排序。
产生的数据为120个数据。蓝色的为稳定排序,红色的为不稳定排序。
排序算法
|
随机数据 |
升序数据 |
降序数据 |
平均运行时间 |
|||
比较次数 |
赋值次数 |
比较次数 |
赋值次数 |
比较次数 |
赋值次数 |
||
插入排序 |
3566 |
3673 |
119 |
0 |
7259 |
7378 |
O(n2) |
快速排序 |
713 |
1443 |
7140 |
22143 |
7140 |
11334 |
O(nlgn) |
随机快排 |
821 |
1882 |
807 |
2116 |
884 |
1992 |
O(nlgn) |
选择排序 |
7260 |
868 |
7260 |
120 |
7260 |
3900 |
O(n2) |
归并排序 |
1065 |
1347 |
616 |
0 |
1012 |
1188 |
O(nlgn) |
冒泡排序 |
7259 |
10341 |
7259 |
0 |
7259 |
21420 |
O(n2) |
希尔排序 |
1608 |
5107 |
1212 |
3027 |
1495 |
4662 |
O(n1+£) |
堆排序 |
119 |
4927 |
119 |
5351 |
119 |
4329 |
O(nlgn) |
由表可以看出在随机情况下快排,堆排序和归并排序的时间复杂度比较低,但是归并要额外花费较大的空间内存,在有序的情况下快排表现很差,所有我们这里有快排的随机化版本,归并和堆排序在有序的情况下也表现很好。
参考文献:
[1] http://baike.baidu.com/view/547263.htm
[2] http://baike.baidu.com/view/157305.htm
[3] http://baike.baidu.com/view/115472.htm
[4] http://baike.baidu.com/view/90797.htm
[5] http://baike.baidu.com/view/396887.htm
[6] Thomas H. Cormen, Charles E. Leiserson, Ronald L. Rivest and Clifford Stein,《Introduction to Algorithms, Second Edition》,The MIT Press ,2001