troysps 2020-07-19
常见的内部排序:插入排序(直接插入、折半插入、希尔排序)、交换排序(冒泡、快排)、选择排序(简单选择、堆排序)、归并排序(2路归并)、基数排序
外排:归并排序(多路归并)、
各种内排的性能比较:
- 每次将一个待排序的记录按关键字大小插入到前面已排好序的子序列中,直到全部记录插入完成
- 每一轮能够确定一个最终位置的记录,某时刻的状态为:[有序序列] Li [无序序列]
- 一般采用就地排序(空间复杂度为O(1))
- 步骤:
- 将第一待排序序列第一个元素看做一个有序序列,把第二个元素到最后一个元素当成是未排序序列。
- 从头到尾依次扫描未排序序列,将扫描到的每个元素插入有序序列的适当位置。(如果待插入的元素与有序序列中的某个元素相等,则将待插入元素插入到相等元素的后面。)
//直接插入 void insertSort(int A[], int n){ int i,j,tmp; for(i=1;i<n;++i){ //从后往前寻找A[i]插入位置 if(A[i]<A[i-1]){ //需要将A[i]插入到前面已排序序列 tmp = A[i]; for(j=i-1;j>=0 && A[j]>tmp;--j){ A[j+1]=A[j]; //往后移动 } A[j+1]= tmp; //插入 } } }
//折半插入 void insertSort2(int A[], int n){ int i,j,tmp,low,high,mid; for(i=1;i<n;i++){ //先折半查找 tmp = A[i]; low=0;high=i-1; while(low<=high){ mid=(low+high)/2; if(tmp<A[mid]) high=mid-1; else low=mid+1; } //往后移动元素 for(j=i-1;j>=high+1;--j) A[j+1]=A[j]; A[high+1]=tmp; //插入 } }
//希尔排序 void shellSort(int A[], int n){ int i,j,tmp,dk; for(dk=n/2;dk>=1;dk /= 2){ //增量变化 for(i=dk;i<n;++i){ if(A[i]<A[i-dk]){ //需要将A[i]插入到前面已排序序列 tmp = A[i]; for(j=i-dk;j>=0 && A[j]>tmp;j -= dk){ A[j+dk]=A[j]; //往后移动 } A[j+dk]= tmp; //插入 } } } }
所谓交换,是指根据两个元素关键字比较的结果来对换这两个记录的位置
//冒泡 void bubbleSort(int A[], int n){ int i,j,tmp; for(i=1;i<n;i++){ //n-1趟 /* for(j=0;j<n-i;++j){ //从前往后冒泡 if(A[j+1]<A[j]){ //每次冒最大值,实现升序 tmp = A[j]; A[j] = A[j+1]; A[j+1] = tmp; } } */ for(j=n-1;j>i;--j){ //从后往前冒泡 if(A[j-1]>A[j]){ //每次冒最小值,实现升序 A[j] = A[j]^A[j-1]; A[j-1] = A[j-1]^A[j]; A[j] = A[j]^A[j-1]; } } } }
//快排 void quickSort(int A[], int low, int high){ int pivot, pivotpos, left, right; if(low < high){ //1.划分 pivot = A[low]; //取第一个元素作为基准 left = low; right = high; while(left<right){ while(left<right && A[right]>=pivot){ --right; } A[left] = A[right]; //把比基准小的元素交换到左边 while(left<right && A[left]<=pivot){ ++left; } A[right] = A[left]; //把比基准值大的元素移动到右边 } A[left] = pivot; //基准元素放到最终位置 pivotpos = left; //2.递归 quickSort(A,low,pivotpos-1); quickSort(A,pivotpos+1,high); } }
每一趟(第i趟)在后面n-i+1个待排序列中选取最小的元素,作为有序子序列的第i个元素,直到n-1趟做完,就只剩1个元素,就不用选了。
//简单选择 void selectSort(int A[], int n){ int i,j,min,tmp; for(i=0;i<n;i++){ //依次确定0到n-1位置上的元素 min=i; for(j=i+1;j<n;j++){ if(A[j]<A[min]) min = j; } if(min!=i){ tmp=A[min]; A[min]=A[i]; A[i]=tmp; } } }
//将元素k向下调整 void AdjustDown(int A[], int k, int n){ int i,tmp; tmp = A[k]; for(i=2*k;i<n;i*=2){ //沿较大的子节点向下筛选 if(i<n && A[i]<A[i+1]){ ++i; //取较大的子节点的下标 } if(A[i] <= tmp) break; //筛选结束 A[k]=A[i]; //将A[i]调整到双亲节点 k=i; //更新k值,继续向下筛选 } A[k]=tmp; //被筛选的点放入最终位置 } //堆排序(大根堆) void heapSort(int A[], int n) { int i,tmp; //建堆,从i=[n,2]~1,反复调整堆(也就是从最后一个叶子节点的父节点开始,往前) for(i=n/2;i>=0;--i){ AdjustDown(A,i,n); } //n-1趟交换和调整堆 for(i=n-1;i>=0;--i){ //输出堆顶元素,和堆底元素交换 tmp=A[0]; A[0]=A[i]; A[i]=tmp; AdjustDown(A,0,i-1); } }
//归并排序 void Merge(int A[], int low, int mid, int high){ int i,j,k; int B[n]={0}; //A的[low...mid]和[mid+1...high]各自有序,将它们合成一个有序表 for(i=low;i<=high;i++){ B[i]=A[i]; } for(i=low,j=mid+1,k=i; i<=mid && j<=high; ++k){ if(B[i]<B[j]) A[k]=B[i++]; //把较小的值拷到A中 else A[k]=B[j++]; } while(i<=mid){ A[k++]=B[i++]; //拷贝前一个表剩余元素 } while(j<=high){ A[k++]=B[j++]; //拷贝后一个表剩余元素 } } void mergeSort(int A[],int low,int high){ if(low<high){ int mid=(low+high)/2; mergeSort(A,low,mid); mergeSort(A,mid+1,high); Merge(A,low,mid,high); //归并 } }
要知道时间复杂度只是描述一个增长趋势,复杂度为O的排序算法执行时间不一定比复杂度为O长,因为在计算O时省略了系数、常数、低阶。实际上,在对小规模数据进行排序时,n2的值实际比 knlogn+c还要小。