排序
算法稳定性
内部排序:在排序期间元素全部存放在内存中的排序
外部排序:在排序期间元素无法全部同时存放在内存中
大多数内部排序都更适用于顺序存储的线性表。
插入排序
直接插入排序
时间复杂度
具有稳定性。
折半插入排序
仅适用于顺序存储的线性表
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| void insertSort(int A[],int n){ int i,j,low,high,mid; for(i=2;i<=n;i++){ A[0]=A[i]; low=1; high=-1; while(low<=high){ mid=(low+high)/2; if(A[mid]>A[0]) high=mid-1; else low=mid+1; } for(j=i-1;j>=high+1;--j) A[j+1]=A[j]; A[high+1]=A[0]; } }
|
有稳定性,时间复杂度和直接插入排序一样。
希尔排序
取增量,然后不断缩小增量,直到增量为1
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| void shellsort(int a[],int n){ int dk,i,j; for(dk=n/2;dk>=1;dk=dk/2){ for(int i=dk+1;i<=n;i++){ if(A[i]<A[i-dk]){ A[0]=A[i]; for(j=i-dk;j>0&& A[0]<A[j];j-=dk){ A[j+dk]=A[j]; } A[j+dk]=A[0]; } } }
|
空间复杂度为
时间复杂度为
不稳定
交换排序
冒泡排序
1 2 3 4 5 6 7 8 9 10 11 12 13
| void bubblesort(int a[],int n){ for(int i=0;i<n-1;i++){ bool flag=false; for(int j=n-1;j>i;j--){ if(A[j-1]>A[j]){ swap(A[j-1],A[j]); flag=true; } if(flag==false) return; } } }
|
时间复杂度为
有稳定性
快速排序
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| void quick_sort(int q[],int l,int r){
if(l>=r) return ; int i=l-1,j=r+1,x=q[l+r>>1]; while(i<j){ do i++;while(q[i]<x); do j--;while(q[j]>x); if(i<j) std::swap(q[i],q[j]); } quick_sort(q,l,j); quick_sort(q,j+1,r); }
|
快速排序是所有内部排序算法中平均性能最优的算法。
不具有稳定性
时间复杂度为
递归次数与每次划分后得到的分区处理顺序无关,与初始数据排列顺序有关
对n个元素进行第一趟快速排序之后,会确定一个基准元素,根据这个基准元素的位置有两个情况:
- 在数组的首端或者尾端,这样子第二次排序之后有两个位置确定。
- 不在数组首端或者尾端,这样两个子序列会至少有三个位置确定
选择排序
简单选择排序
每次都选最小的那个放到当前序列最前面的地方。
不稳定,时间复杂度
1 2 3 4 5 6 7
| for(int i=0;i<n-1;i++){ int min=i; for(int j=i+1;j<n;j++){ if(A[j]<A[min]) min=j; } if(min!=i) swap(A[min],A[i]); }
|
堆排序
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| void maxHeapify(vector<int>&a,int i,int heapSize){ int l=2*i+1; int r=2*i+2; int largest=i; if(l<heapSize && a[l]>a[largest]){ l=largest; } if(r<heapSize && a[r]>a[largest]){ r=largest; } if(largest!=i){ swap(a[largest],a[i]); maxHeapify(a,largest,heapSize); } }
void buildMaxHeap(vector<int>&a,int heapsize){ for(int i=heapsize/2;i>=0;i--){ maxHeapify(a,i,heapsize); } } int findKthLargest(vector<int>& nums, int k) { int heapSize=nums.size(); buildMaxHeap(nums,heapSize); for(int i=nums.size()-1;i>=nums.size()-k+1;i--){ swap(nums[0],nums[i]); --heapSize; maxHeapify(nums,0,heapSize); } return nums[0]; }
|
时间复杂度为
不稳定
若从一亿个数中选出前100个最大的数:
首先选择前100个数建立小顶堆
逐渐读入数据,如果小于堆顶就删除,如果大于就替换掉堆顶,然后重新调整堆。
归并排序,基数排序和计数排序
归并排序
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| void merge(int a[],int b[],int low,int mid,int high){ int i,j,k; for(k=low;k<=high;k++){ b[k]=a[k]; } for(i=low,j=mid+1,k=i;i<=mid && j<=high; k++){ if(B[i]<=B[j]) A[k]==b[i++]; 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); } }
|
时间复杂度为
有稳定性
基数排序
d个关键字,以r为基数。
举个例子,每个关键字都是1000以下的正整数,基数r=10,先按照最低位子关键字(个位)来排序,然后是十位,最后是百位。
空间复杂度
空间复杂度
计数排序
对于每个待排序的元素x,统计小于x的元素的位置。
1 2 3 4 5 6 7 8 9 10 11
| void CountSort(int a[],int b[],int n,int k){ int i,C[k]; for(i=0;i<k=i++) C[i]=0; for(i=0;i<n;i++) C[A[i]]++; for(i=1;i<n;i++) C[i]=C[i]+C[i-1]; for(int i=n-1;i>=0;i--){ B[--C[A[i]]]=A[i]; } }
|
时间复杂度为
时算法为 ,k不是很大的时候算法时间复杂度不是很大。
外部排序
外部排序时间代价主要是考虑访问磁盘次数,即I/O次数
外部排序通常采用归并排序算法:
- 根据内存缓冲区大小,将外存上的文件分成若干长度为 的子文件,依次读入内存并利用内部排序算法对它们进行排序,并将排序后得到的子文件重新写回内存。
- 对这些归并段进行逐趟归并,使归并端逐渐从小变大,直到得到整个有序文件为止。
外部排序时间=内部排序时间+外存信息读/写时间+内部归并的时间。
多路平衡树和败者树
为了让内部归并不受k的增大的影响,引入败者树(完全二叉树)
k个叶节点分别存放k个归并段在归并过程中当前参与比较的元素,内部结点来记录左右子树中的失败者,胜利者向上比较,直到根节点。大的为失败者
置换-选择排序(生成初始归并段)
设初始待排文件为 FI ,初始归并段输出文件为 FO ,内存工作区为 WA,WA可以容纳w个记录。
就是在WA中选一个最小的(并且比FO中最大的还要大)输出到FO中,然后从FI中读入一个数据,继续循环。
直到WA中所有数据比FO最大的数据小,我们换到另一个FO。
最佳归并树
IO次数=2*WPL(树的带权路径长度)
设度为0的结点有 个,度为 的结点有 个,归并树的总结点有 个,则有:
(总结点数=所有结点度数之和+1)
所以严格k叉树有 ,于是有