数据结构--排序总结
排序总类分为:内部排序和外部排序
排序过程主要是对记录的排序码进行比较和记录的移动过程。因此排序的时间复杂性可以算法执行中的数据比较次数及数据移动次数来衡量。
从简单的开始:
插入排序(直接插入,二分插入,希尔插入)
1 、直接插入:初始的时候,拿出一个元素,然后依次加入这个有序表中。
(要点:从最后一个元素比较,不断比较元素,移动元素)
void insertsort(int a[],int n) { int i; for ( i=1; i<n; i++) //i表示插入次数,共进行n-1次插入 { int temp=a[i]; //把待排序元素赋给temp int j=i-1; while ((j>=0)&& (temp<a[j])) { a[j+1]=a[j]; j--; } // 顺序比较和移动 a[j+1]=temp; } }
总结:正直,稳定 花钱是O(n^2)
2 拓展出希尔排序:
分析直插:两个特点:
如果待排序基本有序,那么比较次数大大降低,总数少,效率也就越高。
希尔就是利用这两个特点:
先取一个小于n的整数d1作为第一个增量,把文件的全部记录分组。所有距离为d1的倍数的记录放在同一个组中。先在各组内进行直接插入排序;然后,取第二个增量d2<d1重复上述的分组和排序,直至所取的增量dt=1(dt<dt-l<…<d2<d1),即所有记录放在同一组中进行直接插入排序为止。分组内插入排序。
一般的初次取序列的一半为增量,以后每次减半,直到增量为1。
void ShellSort(int a[],int n) { int d,i,j,temp; for(d=n/2;d>=1;d=d/2) //从增量开始 { for(i=d;i<n;i++) //第一趟排序的第二组,第三组.... { temp=a[i]; //要排序元素赋值给一个temp for(j=i-d;(j>=0)&&(a[j]>temp);j=j-d) //进行插入排序 { a[j+d]=a[j]; } a[j+d]=temp; } } }
总结:是对直接插入的改进,但是缺点是变成不稳定的了。希尔顿是个不稳定的人,性格暴躁。
3、折半插入(二分插入)
void BinaryInsertSort(int a[],int n) { int i,j; for(i=1; i<n; i++) //都会令初始有1个元素有序,因此进行n-1次 { int left=0,right=i-1; int temp=a[i];//最好放一个temp ,我们规定以后就这样做哈 while(left<=right) { int middle=(left+right)/2; //由于多次取中点,所以放到循环内部 if(temp<a[middle]) right=middle-1; else left=middle+1; } //取右区间 for(j=i-1;j>=left;j--) a[j+1]=a[j]; //元素后移空出插入位 a[left]=temp; } }
总结:虽然二了点,但是做事还是很稳定 花钱是O(n^2)
交换排序:(冒泡排序、快速排序、
冒泡排序:不断比较相邻元素大小,进行交换最终生成有序(每一次交换都会把最大的给放到最后)
void BubbleSort(int a[],int n) { int i,j,temp; int flag=1; while(flag){ flag=0; for(i=0;i<n;i++){ for(j=0;j<n-1;j++){ if(a[j]>a[j+1]){ temp=a[j+1]; a[j+1]=a[j]; a[j]=temp; flag=1; } } } } }
总结:花钱一样是 O(n^2) ,是内部排序最慢的一个。泡泡比较稳定哈。
拓展:改进冒泡的版本——快速排序。
想法:
首元素作为轴记录,从前、后双向扫描序列,通过交换,实现大值记录后移,小值记录前移,最终将轴记录安置在一个适当的位置。(小值记录在前、大值记录在后)
然后,分两部分进行一次设置记录
分割算法: 用临时变量对轴备份 取两个指针low和high,它们的初始值就是序列的两端下标,在整个过程中保证low不大于high 移动两个指针 首先从high所指的位置向左搜索,找到第一个小于轴的元素, 把这个元素放在low的位置 再从low开始向右,找到第一个大于轴的元素,把它放在high的位置
要点:要分割多少次
快速排序有两个过程:
一个是分割过程:(完成左边小,右边大的过程,这两部分并不是有序)
1 int Partition(int a[], int low, int high){ 2 int pivot = a[low]; //选取第一个 3 while(low < high){ //在作为快排序的子程序时不用 4 while(low < high && a[high] >= pivot) 5 high --; 6 a[low] = a[high];//在此处担心会不会覆盖 7 while(low < high && a[low] <= pivot) 8 low++; 9 a[high] = a[low]; //因为这一步取走了low的值 10 } //在作为快排序的子程序时不用 11 a[low] = pivot; 12 return low; 13 }
一个是排序过程,需要对上面分割后的两部分进行递归调用:
int Partition(int a[], int low, int high){ int pivot = a[low]; //选取第一个 while(low < high){ //在作为快排序的子程序时不用 while(low < high && a[high] >= pivot) high --; a[low] = a[high];//在此处担心会不会覆盖 while(low < high && a[low] <= pivot) low++; a[high] = a[low]; //因为这一步取走了low的值 } //在作为快排序的子程序时不用 a[low] = pivot; return low; }
总结:最好O(log2n),最坏的空间复杂度为O(n^2)。排序太快了,肯定不稳定哈。
选择排序
简单选择,堆排序,
1 ////注意:C语言没有引用 2 void Swap(int* a,int* b){ 3 int temp; 4 temp=*a; 5 *a=*b; 6 *b=temp; 7 8 } 9 void SelectSort ( int a[], int n) { 10 int i,k,j; 11 for ( i = 0; i <n-1; i++ ) { 12 k = i; //选择具有最小排序码的对象 13 for ( j = i+1; j < n; j++) 14 if ( a[j] < a[k] ) 15 k = j; //当前具最小排序码的对象 16 if ( k != i ) //对换到第 i 个位置 17 Swap ( &a[i], &a[k] ); 18 } 19 }
树形选择排序,也称为竞标赛排序,每次找出最小的,然后,令其为无穷大:
引入:例,8 名运动员要决出 冠、亚、季军。 如果用上面的需要 7+6+5才能选出冠亚军,而用树形只需要11场就可以啦。

堆排序:一棵完全二叉树,任一个非终端结点的值均小于等于(或大于等于)其左、右儿子结点的值。
分为大顶堆和小顶堆:
构造初始堆:首先元素按照一层一层方式构建一个完全二叉树。然后需要构造初始堆,则从最后一个非叶节点开始调整:
每次调整都是从父节点、左孩子节点、右孩子节点三者中选择最大者跟父节点进行交换(交换之后可能造成被交换的孩子节点不满足堆的性质,
因此每次交换之后要重新对被交换的孩子节点进行调整)。有了初始堆之后就可以进行排序了。
(构造第一次满足堆的性质,但还不具备有序)
1 C++实现 2 #include <iostream> 3 #include<algorithm> 4 using namespace std; 5 6 void HeapAdjust(int *a,int i,int size) //调整堆 7 { 8 int lchild=2*i; //i的左孩子节点序号 9 int rchild=2*i+1; //i的右孩子节点序号 10 int max=i; //临时变量 11 if(i<=size/2) //如果i是叶节点就不用进行调整 12 { 13 if(lchild<=size&&a[lchild]>a[max]) 14 { 15 max=lchild; 16 } 17 if(rchild<=size&&a[rchild]>a[max]) 18 { 19 max=rchild; 20 } 21 if(max!=i) 22 { 23 swap(a[i],a[max]); 24 HeapAdjust(a,max,size); //避免调整之后以max为父节点的子树不是堆 25 } 26 } 27 } 28 29 void BuildHeap(int *a,int size) //建立堆 30 { 31 int i; 32 for(i=size/2;i>=1;i--) //非叶节点最大序号值为size/2 33 { 34 HeapAdjust(a,i,size); 35 } 36 } 37 38 void HeapSort(int *a,int size) //堆排序 39 { 40 int i; 41 BuildHeap(a,size); 42 for(i=size;i>=1;i--) 43 { 44 //cout<<a[1]<<" "; 45 swap(a[1],a[i]); //交换堆顶和最后一个元素,即每次将剩余元素中的最大者放到最后面 46 //BuildHeap(a,i-1); //将余下元素重新建立为大顶堆 47 HeapAdjust(a,1,i-1); //重新调整堆顶节点成为大顶堆 48 } 49 } 50 51 int main(int argc, char *argv[]) 52 { 53 //int a[]={0,16,20,3,11,17,8}; 54 int a[100]; 55 int size; 56 while(scanf("%d",&size)==1&&size>0) 57 { 58 int i; 59 for(i=1;i<=size;i++) 60 cin>>a[i]; 61 HeapSort(a,size); 62 for(i=1;i<=size;i++) 63 cout<<a[i]<<""; 64 cout<<endl; 65 } 66 return 0; 67 }
总结:堆房子,需要很多砖头,非常不稳定。最坏的复杂度为o(log2n)
归并排序的思想是:分解 n/2,n/2/2...1 ,合并(排序):
1 #include <stdlib.h> 2 #include <stdio.h> 3 //将有序的X[s..u]和X[u+1..v]归并为有序的Z[s..v] 4 void merge(int X[], int Z[], int s, int u, int v) //z是一个辅助数组 5 { 6 int i, j, q; 7 i = s; 8 j = u + 1; 9 q = s; 10 11 while( i <= u && j<= v ) 12 { 13 if( X[i] <= X[j] ) 14 Z[q++] = X[i++]; //要记住,连个序列已经有序,找出最小的插入就可以了。 15 else 16 Z[q++] = X[j++]; 17 } 18 19 while( i <= u ) //将X中剩余元素X[i..u]复制到Z 20 Z[q++] = X[i++]; 21 while( j <= v ) //将X中剩余元素X[j..v]复制到Z 22 Z[q++] = X[j++]; 23 } 24 25 /* X[0..n-1]表示参加排序的初始序列 26 * t为某一趟归并时子序列的长度 27 * 整型变量i指出当前归并的两个子序列中第1个子序列的第1个元素的位置 28 * Y[0..n-1]表示这一趟归并后的结果 29 */ 30 void mergePass(int X[], int Y[], int n, int t) //y是生成的辅助数组 31 { 32 int i = 0, j; 33 while( n - i >= 2 * t ) //将相邻的两个长度为t的各自有序的子序列合并成一个长度为2t的子序列 34 { 35 merge(X, Y, i, i + t - 1, i + 2 * t - 1); 36 i = i + 2 * t; 37 } 38 39 if( n - i > t ) //若最后剩下的元素个数大于一个子序列的长度t时 40 merge(X, Y, i, i + t - 1, n - 1); 41 else //n-i <= t时,相当于只是把X[i..n-1]序列中的数据赋值给Y[i..n-1] 42 for( j = i ; j < n ; ++j ) 43 Y[j] = X[j]; 44 } 45 46 void mergeSort(int X[], int n) //归并排序 47 { 48 int t = 1; //初始序列长度为1 49 int *Y = (int *)malloc(sizeof(int) * n); //需要n辅助空间 50 while( t < n ) 51 { 52 mergePass(X, Y, n, t); //将两两长度为1的序列归并 53 t *= 2; //归并后变为2 54 mergePass(Y, X, n, t); 55 t *= 2; 56 } 57 free(Y); 58 } 59 60 void print_array(int array[], int n) 61 { 62 int i; 63 for( i = 0 ; i < n ; ++i ) 64 printf("%d ", array[i]); 65 printf("\n"); 66 } 67 68 int main() 69 { 70 int array[] = {65, 2, 6, 1, 90, 78, 105, 67, 35, 23, 3, 88}; 71 int size = sizeof(array) / sizeof(int); 72 mergeSort(array, size); 73 print_array(array, size); 74 return 0; 75 }
总结:稳定,尽管归并排序最坏情况的比较次数比快速排序少,但它需要更多的元素移动,因此,它在实用中不一定比快速排序快
如何选择:
(1) 当待排序元素的个数n较大,排序码分布是随机,而对稳定性不做要求时,则采用快速排序为宜。
(2)当待排序元素的个数n大,内存空间允许,且要求排序稳定时,则采用二路归并排序为宜。
(3)当待排序元素的个数n大,排序码分布可能会出现正序或逆序的情形,且对稳定性不做要求时,则采用堆排序或二路归并排序为宜。
(4)当待排序元素的个数n小,元素基本有序或分布较随机,且要求稳定时,则采用直接插入排序为宜。
(5)当待排序元素的个数n小,对稳定性不做要求时,则采用直接选择排序为宜,若排序码不接近逆序,也可以采用直接插入排序。冒泡排序一般很少采用。
对元素个数较多的排序,可以选快速排序、堆排序、归并排序,元素个数较少时,可以选简单的排序方法。

浙公网安备 33010602011771号