本节主要讨论九大排序算法,排序对数据结构中一个最重要的部分,同时也是后面数据结构的基础所有必须要先有所理解:
排序分为内部排序和外部排序两种,内部排序是数据记录在内存进行的排序,外部排序是由于数据量太大,不得不采取的措施,排序过程中需要访问外部内存。
对于数据量N 很大时,不得不采用时间复杂度o(nlog2n)的排序方法:快速排序,堆排序和归并排序。快速排序目前基于内部排序中最好的算法,适应随机分布关键词数据
一.插入排序(直接插入排序):对数组进行排序或者不能修改数组话对数组地址排序
将一个外部数据插入一个已经排位完有序sequence中:第一个记录为有序subsequnce,接下来的在依次的插入。
要点:设立哨兵,作为临时存储和判断数组边界只用,插入排序最稳定的,o(n^2).对于其他插入排序有二分插入排序
1 #include<cstdlib> 2 #include<iostream> 3 4 void print(int a[], int n ,int i){ 5 6 for(int j= 0; j<8; j++){ 7 printf("%2d",a[j]); 8 } 9 10 } 11 void InsertSort(int a [],int n) 12 {int sold=0,j=0; 13 for(int i=1;i<n;i++) 14 { 15 j=i-1; 16 sold=a[i]; 17 while(sold<a[j]) 18 { 19 a[j+1]=a[j]; 20 j--; 21 } 22 a[j+1]=sold; 23 } 24 } 25 int main() 26 { 27 int a[8]={3,1,5,7,2,4,9,6}; 28 InsertSort(a,8); 29 print(a,8,0); 30 return 0; 31 }
使用python语言进行编写,注意到c++、python之间的差别,-1在python中表示的最末尾元素:
1 def InsetSort(a,n): 2 sold=0; 3 for i in range(1,n): 4 5 j=i-1 6 sold=a[i] 7 8 "插入:移动位置和寻找插入点,从后往前遍历" 9 while(sold<a[j] and j>=0): 10 "从后往前遍历找到最佳插入点退出" 11 a[j+1]=a[j] 12 j=j-1 13 a[j+1]=sold 14 print a 15 return a
二.插入排序(希尔排序):又叫 缩小量排序,按照Gap分组,,将序列(i,i+Gap,i+2Gap,...)数据分到同一组。在组内对数据进行插入排序
将整个序列分割成若干子序列直接进行插入排序,最后对整个序列进行插入排序
操作方法:
- 选择一个增量序列t1,t2,…,tk,其中ti>tj,tk=1;
- 按增量序列个数k,对序列进行k 趟排序;
- 每趟排序,根据对应的增量ti,将待排序列分割成若干长度为m 的子序列,分别对各子表进行直接插入排序。仅增量因子为1 时,整个序列作为一个表来处理,表长度即为整个序列的长度。
-
第一步:增量序列d={n/2,n/4,....1}取其上界【2.4】=3,n 为排序的个数,因此n=5,第二趟d=3,第三趟d=1.
- 先将要排序的一组记录按某个增量d(n/2,n为要排序数的个数)分成若干组子序列,每组中记录的下标相差d.对每组中全部元素进行直接插入排序,然后再用一个较小的增量(d/2)对它进行分组,在每组中再进行直接插入排序。继续不断缩小增量直至为1,最后使用直接插入排序完成排序。
最重要的是组的划分:第一趟:Gap=N/2,将N=10排序序列分为5组,下标i,i+Gap的数划分到同一组内,之前(49,13),....
在第二趟:Gap=Gap/2 此时组内序列增加同样对组内数据进行划分:
依次类推直到Gap=1,不需要进行划分
1 void ShellInsertSort(int a [],int n,int dk) 2 { int sold=0,j=0; 3 //将序列分为M个子序列:M=gap=n/2,第一次排序时候分为10/2=5组,下表i,i+dk分为同一组进行 4 //插入排序。 5 for(int i=dk;i<n;++i) 6 { sold=a[i]; 7 j=i-dk; 8 while(sold<a[j] && j>=0) 9 { 10 a[j+dk]=a[j]; 11 j=j-dk; 12 } 13 a[j+dk]=sold; 14 print(a,n,i); 15 } 16 printf("%3d 趟排序完毕",dk); 17 } 18 void ShellSort(int a[],int n) 19 { 20 int Dk=n/2;//Dk 序列的增量 21 while(Dk>=1) 22 { 23 ShellInsertSort(a,n,Dk); 24 Dk=Dk/2; 25 } 26 } 27 int main() 28 { 29 int a[8]={3,1,4,7,9,10,6,5}; 30 //InsertSort(a,8); 31 ShellSort(a,8); 32 print(a,8,0); 33 return 0; 34 }
python Code
1 def shellSort(a,n): 2 Gap=int(n/2)#进行的是n/2均分,从Gap+n进行计数 3 while Gap>=1: 4 "Gap 是其分组" 5 for i in range(Gap,n): 6 sold=a[i] 7 j=i-Gap 8 while(j>=0 and sold<a[j]): 9 a[j+Gap]=a[j] 10 j=j-Gap 11 a[j+Gap]=sold 12 13 Gap=int(Gap/2) 14 return a 15
3.冒泡排序:由上往下遍历,较大数往下垂,较小数往上升。在每一次遍历过程中,让其中1-未拍完序列中最大的数往下沉.最重要的一点是每一次遍历中swap交换有变换的值。
时间复杂度o(n^2)
1 void BubbleSort(int a[],int n) 2 { int temp=0; 3 for (int i=0;i<n-1;i++) 4 { 5 for (int j=0;j<n-1-i;j++) 6 { 7 if (a[j]>a[j+1]) 8 { 9 //交换两者间的顺序 10 temp=a[j]; 11 a[j]=a[j+1]; 12 a[j+1]=temp; 13 } 14 } 15 } 16 }
3.2 对冒泡排序进行改进:
1.对冒泡排序加入一个exchange的标志性参数,设置一标志性变量pos,用于记录每趟排序中最后一次进行交换的位置。由于pos位置之后的记录均已交换到位,故在进行下一趟排序时只要扫描到pos位置即可
这个改近的算法是对位置进行修改的pos,pos之后数据完全排好序的数据,不需要进行遍历,减少遍历的次数
1 { 2 //对冒泡排序进行修改进,记录最后一次进行交换的地址,再一次遍历的时候到此地址之前就可以结束 3 int i=n-1,temp=0; 4 while(i>0) 5 { 6 int pos=0; 7 for(int j=0;j<i;j++) 8 if(a[j]>a[j+1]) 9 { 10 pos=j; 11 temp=a[j]; 12 a[j]=a[j+1]; 13 a[j+1]=temp; 14 } 15 i=pos; 16 } 17 }
2.第二种修改在于:从前后进行冒泡排序,后面排序数据下沉,前者对数据上升
传统冒泡排序中每一趟排序操作只能找到一个最大值或最小值,我们考虑利用在每趟排序中进行正向和反向两遍冒泡的方法一次可以得到两个最终值(最大者和最小者) , 从而使排序趟数几乎减少了一半。
改进后的算法实现为:
1 def BubbleSort(a,n): 2 low=0 3 hight=n-1 4 5 while low<hight: 6 ##正向排序找到最大 7 for i in range(low,hight): 8 if a[i]>a[i+1]: 9 tmp=a[i] 10 a[i]=a[i+1] 11 a[i+1]=tmp 12 13 hight=hight-1 14 j=hight 15 ##反向找到最小 16 while j>low: 17 if a[j]<a[j-1]: 18 tmp=a[j] 19 a[j]=a[j-1] 20 a[j-1]=tmp 21 j=j-1 22 low=low+1 23 24 print a
4.快速排序:挖一个坑填一个坑,只有在坑填完之后指针才能够移动,特色在于使用两个指针i,j分别查找大于基数(往后端移动),小于基数(往前端移动)。遵守先后再前的规则。
时间复杂度:o(nlog2n),log2n不是logn 在于每一次循环有两次查找。同时快速排序在同量级认为是最好的排序,特别适用随机数序列,如果原序列按照有序方式进行,快速排序退变成冒泡排序.为改进之,通常以“三者取中法”来选取基准记录,即将排序区间的两个端点与中点三个记录关键码居中的调整为支点记录。快速排序是一个不稳定的排序方法
基本思想:最主要思想是挖坑和填坑,选a[0]为基,相当于从该序列中找到一个数填不a[0]这个坑,遵守
先从后网前找,在从前往后找,直到i++(i=0),j--(j=end),i>J退出循环.
1 void quick_sort(int a[],int low,int hig ) 2 { 3 if(low < hig) 4 { 5 int privotLoc=AdjustArry(a,low,hig); 6 quick_sort(a,low,privotLoc); 7 quick_sort(a,privotLoc+1,hig); 8 } 9 } 10 11 void swap(int *a, int *b) 12 { 13 int tmp = *a; 14 *a = *b; 15 *b = tmp; 16 } 17 18 int AdjustArry(int a[],int l,int r) 19 { 20 //开始设置两个指针,分别从最低位和最高位开始 21 int i=l,j=r; 22 int X=a[l];//坑元素 23 while(i<j) 24 { 25 //先从后开始,再从前开始,从后面找出一个数小于基准,前数大于基准 26 while(a[j]>X && i<j) 27 {//必须要找到比基准小元素移动到前端 28 --j; 29 } 30 swap(&a[l],&a[j]);//将a[i]位置的坑填满,空出来a[j]这个坑 31 32 while(a[i]<X && i<j) 33 { 34 ++i; 35 //找到比基准大的元素 36 } 37 swap(&a[i],&a[j]); 38 39 } 40 print (a,10,0); 41 return i; 42 } 43 44 void quick_sort(int a[],int low,int hig ) 45 { 46 if(low < hig) 47 { 48 int privotLoc=AdjustArry(a,low,hig); 49 quick_sort(a,low,privotLoc); 50 quick_sort(a,privotLoc+1,hig); 51 } 52 } 53 54 int main() 55 { 56 int a[10]={3,1,5,7,2,4,9,6,10,8}; 57 //InsertSort(a,8); 58 //ShellSort(a,8); 59 //BubbleSort1(a,8); 60 quick_sort(a,0,9); 61 print(a,10,0); 62 return 0; 63 }
对快速排序进行一定的改进:对长度大于K子序列进行快速排序,整个基本有序序列进行插入排序,先对大于K的序列进行排序,让整个序列有较有序的排序,在对整个序列进行插入排序,K=8效果最好。
在本改进算法中,只对长度大于k的子序列递归调用快速排序,让原序列基本有序,然后再对整个基本有序序列用插入排序算法排序。实践证明,改进后的算法时间复杂度有所降低,且当k取值为 8 左右时,改进算法的性能最佳。算法思想如下:
1 void swap(int *a, int *b) 2 { 3 int tmp = *a; 4 *a = *b; 5 *b = tmp; 6 } 7 8 int AdjustArry(int a[],int l,int r) 9 { 10 //开始设置两个指针,分别从最低位和最高位开始 11 int i=l,j=r; 12 int X=a[l];//坑元素 13 while(i<j) 14 { 15 //先从后开始,再从前开始,从后面找出一个数小于基准,前数大于基准 16 while(a[j]>X && i<j) 17 {//必须要找到比基准小元素移动到前端 18 --j; 19 } 20 swap(&a[l],&a[j]);//将a[i]位置的坑填满,空出来a[j]这个坑 21 22 while(a[i]<X && i<j) 23 { 24 ++i; 25 //找到比基准大的元素 26 } 27 swap(&a[i],&a[j]); 28 29 } 30 print (a,10,0); 31 return i; 32 } 33 34 void quick_sort_improve(int a[],int low,int hig,int k ) 35 { 36 if(hig-low>k) 37 { 38 int privotLoc=AdjustArry(a,low,hig); 39 quick_sort_improve(a,low,privotLoc,k); 40 quick_sort_improve(a,privotLoc+1,hig,k); 41 } 42 } 43 void quick_sort(int a[],int low,int hig,int k) 44 { //对大于k的序列进行快速排序 45 quick_sort_improve(a,low,hig,k); 46 //对整个序列进行插入排序,对小序列不需要进行递归排序,速度更快。K=8效果最好 47 for (int i=1;i<=hig;i++) 48 { 49 int tmp=a[i]; 50 int j=i-1; 51 while(tmp<a[j]) 52 { 53 a[j+1]=a[j]; 54 j=j-1; 55 56 } 57 a[j+1]=tmp; 58 } 59 } 60 int main() 61 { 62 int a[10]={3,1,5,7,2,4,9,6,10,8}; 63 //InsertSort(a,8); 64 //ShellSort(a,8); 65 //BubbleSort1(a,8); 66 quick_sort(a,0,9,8); 67 print(a,10,0); 68 return 0; 69 }
5.归并排序:采用分而制之,递归过程中进行。
将两个以上有序表合并成新的有序表,
合并方法:
1.设a[i.....n]由两个子序列a[i....m]和a[m+1....n].长度m-i+1,n-m
2.j=m+1,k=i//
3. i>m或j>n
4.选取a[i]和a[j]较小的存入辅助数组
****
对于两个有序数列合并, 第一次比较俩个有序序列第一个值,小的取出,依次比较,知道有一个集合为空,将其余下依次填补到集合中就行:
1 void MemerArry(int a[],int b[],int n,int m,int c[] 2 { 3 int i,j,k=0;i=0;j=0; 4 while(i <n && j<m) 5 { 6 if(a[i]<a[j]) 7 c[k++]=a[i++]; 8 else 9 { 10 c[k++]=b[j++]; 11 } 12 13 } 14 while(i<n) 15 { 16 c[k++]=a[i++]; 17 } 18 while(j<m) 19 { 20 c[k++]=b[j++]; 21 } 22 }
1 void MemerArry(int a[],int b[],int n,int m,int c[] 2 { 3 int i,j,k=0;i=0;j=0; 4 while(i <n && j<m) 5 { 6 if(a[i]<a[j]) 7 c[k++]=a[i++]; 8 else 9 { 10 c[k++]=b[j++]; 11 } 12 13 } 14 while(i<n) 15 { 16 c[k++]=a[i++]; 17 } 18 while(j<m) 19 { 20 c[k++]=b[j++]; 21 } 22 }
对于归并排序之前两个有序的子序列合并一样,将原来序列依次二分,知道二分到只有一个元素子序列为止,最后将单个元素的子序列依次采用上续方式进行合并。
1 void Merger_Array(int a[],int first,int mid,int last,int temp[]) 2 { 3 //使用三个数将a划分成需要的A,B两个子序列 4 int i=first,j=mid+1,k=0; 5 6 int m=mid,n=last; 7 8 while(i<=m && j<=n) 9 { 10 if (a[i] <= a[j]) 11 temp[k++] = a[i++]; 12 else 13 temp[k++] = a[j++]; 14 } 15 while (i <= m) 16 temp[k++] = a[i++]; 17 18 while (j <= n) 19 temp[k++] = a[j++]; 20 21 for( i=0;i<k;i++) 22 {//将会带到其中 23 a[first+i]=temp[i]; 24 } 25 } 26 void Merger_sort(int a[],int first,int last,int temp[]) 27 { 28 //必须要使用递归方式,先对整个序列进行分割成最小子序列,最后才将最小子序列进行合并 29 if(first<last) 30 { 31 int mid=(first+last)/2; 32 Merger_sort(a,first,mid,temp);//左子序列划分到最小单位 33 Merger_sort(a,mid+1,last,temp); 34 //合并两者序列 35 Merger_Array(a,first,mid,last,temp); 36 } 37 } 38 39 bool Merger(int a[],int n) 40 { 41 int *p =new int[n]; 42 if(n==0 || p==NULL) 43 return false; 44 Merger_sort(a,0,n-1,p); 45 print(a,n,0); 46 return true; 47 48 }
6.选择排序:简单选择排序和堆排序:
简单排序:排序的一组数,从中选出最大一个数与第一个位置的数进行交换,之后在剩余的序列中选择最大的数与第2个位置进行交换,依次进行类推,直到第n-1个元素为止。
1 void select_max(int a [],int Index,int n) 2 { 3 int max=-INF,maxIndex=0; 4 for(int i=Index;i<n;i++) 5 { 6 if(a[i]>max) 7 { 8 max=a[i]; 9 maxIndex=i; 10 } 11 } 12 int tmp=a[Index]; 13 a[Index]=a[maxIndex]; 14 a[maxIndex]=tmp; 15 } 16 17 void Select_sort(int a[],int n) 18 { 19 //选择排序过程中最重要是选出元素和已知位置进行交换 20 for (int i=0;i<n;i++) 21 { 22 select_max(a,i,n); 23 } 24 printf("从大往小排序: "); 25 print(a,n,0); 26 }
2.堆排序:
定义:对于n个元素的sequence,有如下定义:前者称之为最大堆,后者称之为最小堆,堆是一种完全二叉树,其非叶子节点的值不会大于其子女值(最小堆中),并且i位置的父节点两个子节点在于2i、2i+1处。
这里的根节点存储位置是从i=1开始,子女节点2i,2i+1.
如下所示,左图根节点从1开始,右图从0开始
对于堆排序流程,因为排序,按照树的结构进行存储。,堆有一个特点:根节点元素最小值
解决两个问题:1.如何将n个待排序元素建立成堆,
2.当堆顶元素输出后如何调节剩余n-1个元素,让其形成一个新堆。
开始对二个问题进行理解,当堆顶元素被删除后,如何调节堆
初始堆:
1.第n个结点必定是第[n/2]个根节点的子节点,可以从第[n/2]根节点开始进行堆的筛选,从[n/2]---0个根节点进行筛选。就可以建立初始堆
算法难点分析:第一个在于建立初始堆,第二个如何对堆元素进行选择,在建立初始堆的过程中对无序的序列的不断选择过程。最小堆为列。
1 #include<cstdlib> 2 #include<iostream> 3 #include<cstdio> 4 void print(int a[], int n){ 5 for(int j= 0; j<n; j++){ 6 printf("%4d",a[j]); 7 } 8 printf(" "); 9 } 10 11 void Heap_Adjust(int a[],int s,int n) 12 { 13 int tmp=a[s]; 14 int child=2*s+1;//定义的是左节点 15 while(child<n) 16 { 17 //对其左右子节点最小元素交换,从左右节点中选择最小原素进行交换 18 if(child+1<n && a[child+1]<a[child] ) 19 { 20 ++child; 21 } 22 //开始进行交换,最小堆排序,只有当子比父小 23 if (a[s]<a[child]) 24 break; 25 26 a[s]=a[child]; 27 s=child; 28 child=2*s+1; 29 30 //将要调整的数放到需要调整位置上 31 a[s]=tmp; 32 } 33 34 } 35 36 void Building_Heap(int a[],int n) 37 { 38 for(int i=n/2-1;i>=0;--i) 39 { 40 Heap_Adjust(a,i,n); 41 printf("%3d %3d ",i,2*i+1); 42 print(a,n); 43 } 44 } 45 void Heap_sort(int a[],int n) 46 { 47 //从[n/2]根节点开始筛选建立初始堆 48 Building_Heap(a,n); 49 printf("初始堆:"); 50 print(a,n); 51 52 /*for(int i=n-1;i>0;--i) 53 { 54 //堆输出时候必须对堆进行调整 55 int temp=a[i]; 56 a[i]=a[0]; 57 a[i]=temp; 58 Heap_Adjust(a,0,i); 59 }*/ 60 } 61 62 63 int main(){ 64 int H[8] = {91,53,30,47,85,24,36,12}; 65 printf("初始值"); print(H,8); 66 67 Heap_sort(H,8); 68 69 70 printf("结果值"); 71 print(H,8); 72 73 }
8.基数排序:
基排序:将无序的序列分为多个组,通过某种规则将原序列中的数按照此种规则分配到桶中,最后将桶中元素在集合起来。最重要例子:扑克牌按照花色、面值进行数据排序。对于一般数据按照个、十、百位的规则方式进行分配到编号为0-9的十个桶中,最后依次按照桶的顺序将数据集合起来。将集合起新数据重复同样规则,再桶排序中采用其他排序方式或者直接采用桶排序,对无序集合进行排序,在采用桶排序不需要进行数据比较。
方式:
1.将原始数据按照个位进行桶排序分配,虽然有多个数据落到同一个桶中,形成新数据X
2.注意将之前桶进行数据的清零,将X按照十位分配,这样落在同一个桶中数据进行间接排序,按照桶编号输出新数据X1。
3.同样重复1,2的操作
1 #include<iostream> 2 #include<cstdlib> 3 using namespace std; 4 5 int maxBit(int array[],int n) 6 { 7 int maxvalue=0; 8 for(int i=0;i<n;i++) 9 { 10 if (maxvalue<array[i]) 11 { 12 maxvalue=array[i]; 13 } 14 } 15 int D=0; 16 while(maxvalue>0) 17 { 18 D+=1; 19 maxvalue=maxvalue/10; 20 } 21 return D; 22 } 23 void Rad_Sort(int array[],int n) 24 { 25 int Count[10];//记载每一位上进行分类的个数 26 int D=maxBit(array,n); 27 int i,j; 28 int k=0; 29 int *tmp =new int[n];//开辟n个空间的int内存,tmp是其首地址 30 int rad=1; 31 while(k<=D)//按照排位的个数 32 { 33 //每一次排序前清零操作 34 for(int i=0;i<10;i++) 35 { 36 Count[i]=0;//Count 类似于10个桶的标签 37 } 38 //根据数据进行分配 39 for(i=0;i<n;i++) 40 { 41 int num=(array[i]/rad)%10; 42 Count[num]++; 43 44 } 45 //数据开始收集,收集后数据重新覆盖原来数据array 46 //输出数据按照0-9的同进行排序,依次输出过程中总共N个数据 47 for (int i=1;i<10;i++) 48 Count[i]=Count[i-1]+Count[i];//这样在Tmp 数据按照原来格式进行显示 49 for(int j=n-1;j>=0;j--) 50 { 51 int num=(array[j]/rad)%10; 52 tmp[Count[num]-1]=array[j]; 53 Count[num]--; 54 } 55 for(j = 0; j < n; j++) //将临时数组的内容复制到data中 56 array[j] = tmp[j]; 57 rad = rad * 10; 58 k++; 59 } 60 61 } 62 void printArray(int array[],int length) 63 { 64 for (int i = 0; i < length; ++i) 65 { 66 cout << array[i] << " "; 67 } 68 cout << endl; 69 } 70 int main() 71 { 72 int array[10] = {73,22,93,43,55,14,28,65,39,81}; 73 Rad_Sort(array,10); 74 printArray(array,10); 75 return 0; 76 }