文章目录
- 1.插入排序
- 2.希尔排序:
- 3.冒泡排序
- 4.快速排序
- 5.简单选择排序
- 6.堆排序
- 在堆中插入新元素:
- 在堆中删除一个元素:
- 7.归并排序
- 8.基数排序
- 9.外部排序
排序算法
1.插入排序
每次将一个待排序的记录按其关键字大小插入到前面已经排好序的子序列中,直到全部记录插入完成。
public void insertSort(int A[],int n){
for(int i=1;i<n;i++){
if(A[i]<A[i-1]){ //后一个元素小于前一个元素
int temp=A[i]; //将后一个元素暂存起来
for(int j=i-1;j>=0 && A[j]>temp;--j){ //检查前面已经排好序的元素
A[j+1]=A[j];//把比这个元素大的值都往后移动
A[j+1]=temp; //复制到插入位置
}
}
}
}
空间复杂度:O(1)
时间复杂度:
最好情况下:原本就有序,每一趟只需要进行关键字对比,不需要移动元素,共n-1趟处理 O(n)
最坏情况下:原本为逆序,每一趟都要对比和移动,比如第i趟,对比i+1次,移动i+2次 O(n^2)
平均时间复杂度 O(n^2)
稳定的排序算法
优化-----折半插入排序
先用折半查找找到应该插入的位置,再移动元素
public void insertSort(int A[],int n){
for(int i=2;i<=n;i++){ //将A[2]-A[n]插入到前面已排序序列
A[0]=A[i]; //将A[i]暂存到A[0]
int low=1;
int high=i-1;
while(low<=high){ //折半查找
int mid=(low+high)/2; //取中间点
if(A[mid]>A[0]){
//查找左半子表
high=mid-1;
}else{
//查找右半子表
low=mid+1;
}
for(int j=i-1;j>=high+1;--j){
A[j+1]=A[j];//向后移元素
A[high+1]=A[0];//插入
}
}
}
}
当low>high时折半查找停止,将[low,i-1]内元素全部右移,并将A[0]复制到low位置
将每个要进行排序的元素先存进A[0]
2.希尔排序:
先将待排序表分割成若干[i,i+d,i+2d…i+kd]的子表,对各个子表分别进行直接插入排序,缩小增量d,重复上面过程,直到d=1为止
public ShellSort(int A[],int n){
for(int d=n/2;d>=1;d=d/2){ //d是增量
for(int i=d+1;i<=n;++i){
if(A[i]<A[i-d]){ //需要将A[i]插入有序增量子表
A[0]=A[i]; //暂存在A[0]
for(int j=i-d;j>0 && A[0]<A[j];j-=d){
A[j+d]=A[j]; /记录后移,查找插入位置
}
A[j+d]=A[0];//插入
}
}
}
}
时间复杂度:和增量序列d1,d2,…dn的选择有关,最坏情况下时间复杂度O(n^2),优于直接插入排序
不稳定的排序算法
仅可用于顺序表,不适合链表
3.冒泡排序
交换排序可分为冒泡排序和快速排序
基于“交换”的排序,比较相邻的两个元素,如果是逆序则进行交换
public void sort(int A[],in n){
for(int i=0;i<n-1;i++){
for(int j=0;j<n-2;j++){
if(A[j]>A[j+1]){
int tmp=A[j];
A[j]=A[j+1];
A[j+1]=tmp;
}
}
}
}
空间复杂度O(1)
时间复杂度:
最好情况下:原本有序:比较次数n-1,交换次数0,O(n)
最坏情况下:全部逆序:比较次数=(n-1)+(n-2)+…+1=n(n-1)/2=交换次数,O(n^2)
4.快速排序
在待排序表[1…n]中任取一个元素pivot作为基准(通常取首元素),通过一趟排序将待排序表划分为独立的两部分[1…k-1][k+1…n],使[1…k-1]中所有元素小于pivot,[k+1…n]中的所有元素大于等于pivot,pivot放在(k)上,不断地进行划分,然后递归地对两个子表重复上面过程,直到每部分只有一个元素或空为止,即所有元素都放在最终位置上。
public int partition(int A[],int low,int high){
int pivot=A[low]; //第一个元素作为基准
while(low<high){ //用low、high搜索基准的最终位置
while(low<high && [high]>=pivot){
--high;
}
A[low]=A[high];//比基准大的元素移动到左端
while(low<high&& A[low]<=pivot){
++low;
}
A[high]=A[low];//比基准大的元素移动到右端
}
A[low]=pivot; //基准元素存放到最终位置
return low; //返回存放基准元素的最终位置
}
快速排序
public void QuickSort(int A[],int low,int high){
if(low<high){ //递归跳出来的条件
int pivotPos=Partition(A,low,high);//划分
QuickSort(A,low,pivotPos-1); //划分左子表
QuickSort(A,pivotPos+1,high);//划分右子表
}
}
时间复杂度O(n*递归层数)
最好:O(nlog2 n) 最坏:O(n^2)
空间复杂度O(递归层数)
最好:O(log2 n) 最坏:O(n)
二叉树的层数就是递归调用的层数
算法主要取决于递归深度,每次划分越均匀,则递归深度越低
如果每次选中的基准将待排序序列划分为很不均衡的两个部分,则会导致递归深度增加,算法效率变低
如果初始序列有序或者逆序,则快速排序的性能最差(因为每次选择都是最靠边元素)
快速排序算法优化思路:
尽量选择可以把数据中分的枢轴元素,比如:选头、中、尾三个位置的元素,取中间值作为枢轴元素;随机选一个元素作为枢轴(基准)元素
不稳定算法
5.简单选择排序
选择排序可分为简单选择排序和堆排序
每一趟在待排序元素中选取关键字最小的元素加入有序子队列
在后面未排序的元素中找到最小的元素,放到前面已经排序好的子队列中
public void SelectSort(int A[],int n){
for(int i=0;i<n-1;i++){ //进行n-1趟
int min=i; //记录最小元素的位置
for(int j=i+1;j<n;j++){ //在A[i...n-1]中选择最小的元素
if(A[j]<A[min]){
min=j; //更新最小元素位置
}
if(min != i){
int tmp=A[i];
A[i]=A[min];
A[min]=tmp;
}
}
}
}
空间复杂度 O(1)
时间复杂度O(n^2)
无论有序、逆序、还是乱序、一定需要n-1趟处理
总共需要对比关键字n(n-1)/2次,元素交换次数<n-1
不稳定的算法
既可以用于顺序表,也可以用于链表
6.堆排序
大根堆 L(i)>=L(2i),L(i)>=L(2i+1); 根节点>=左、右孩子
小根堆 L(i)><=L(2i),L(i)<=L(2i+1); 根节点<=左、右孩子
每一趟将堆顶元素加入有序子序列(与待排序序列中的最后一个元素交换)(堆顶元素和堆底元素互换)
大根堆:检查当前节点是否满足根>=左、右孩子,不满足时,将当前节点与更大的一个孩子互换
//建立大根堆
public void buildMaxHeap(int A[],int n){ // n是元素的总个数
for(int i=n/2;i>0;i--){ //从后往前调节所有非叶子结点 len/2后面的元素都是叶子结点
HeadAdjust(A,i,n);
}
}
//将以k为根的子树调整为大根堆(下坠调整)
public void HeadAdjust(int A[],int k,int n){
//把当前要处理的值放在A[0]位置,防止被覆盖
A[0]=A[k];
//i=2*k就是让它指向当前结点的左孩子
for(int i=2*k;i<=n;i*=2){
// A[i]和A[i+1] 就是比较当前结点的左右孩子谁更大
if(i<n && A[i]<A[i+1]){
i++;
}
if(A[0]>=A[i]){
break; //筛选结束
}else{
A[k]=A[i];//将A[i]调整到双亲结点上
k=i;//修改k值,便于继续向下筛选(小元素下坠 )
}
}
A[k]=A[0];//被筛选结点的值放入最终位置
}
//交换
public void swap(int a,int b){
int tmp=a;
a=b;
b=tmp;
}
//堆排序完整逻辑
public void HeapSort(int A[],int k,int n){
//建立大根堆 O(n)
buildMaxHeap(A,n);
//n-1趟的交换和建堆过程
for(int i=len;i>1;i--){
//堆顶元素和堆底元素互换
swap(A[i],A[1]);
//把剩下的待排序元素整理成堆
HeadAdjust(A,1,i-1);
}
}
时间复杂度:
建堆的过程,关键字对比次数不超过4n,建堆时间复杂度O(n)
根节点最多下坠h-1层,每下坠一层,最多只需对比关键字2次,每一趟时间复杂度不超过 O(log2 n)
共n-1趟,总的时间复杂度为O(nlog2 n)
所以总的时间复杂度为O(n)+O(nlog2 n)=O(nlog2 n)
空间复杂度O(1)
不稳定的算法
基于大根堆排序得到的是递增序列,基于小根堆排序得到的是递减序列
在堆中插入新元素:
比如小根堆:
对于小根堆,新元素放到表尾,与父节点进行对比,若新元素比父节点更小,则将二者交换,新元素就这样一直“上升”,到无法上升为止。每次上升只需要对比关键字1次(与父节点进行对比)
在堆中删除一个元素:
比如小根堆:
删除一个元素后,将堆底的元素放在这个被删除元素的位置,然后让该元素下坠,直到无法下坠为止。
每次下坠可能需要进行比较关键字两次或者一次(比较两次:左右孩子对比一次,选出最小的那个孩子,再由该下坠元素和最小的孩子进行对比,如果比最小的孩子小,就和最小的孩子交换位置)
7.归并排序
把两个或者多个已经有序的序列合并成一个
m路归并,每选出一个元素需要对比关键字m-1次
public merge(int A[],int low,int mid,int high){
int[] B=new int[A.length+1];
int i=0,j=0,k=0;
for(k=low;k<=high;k++){
//将A中所有元素复制到B中
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++]; //将较小的值复制到A中
}else{
A[k]=B[j++];
}
}
//没有归并完的部分复制到尾部
while(i<=mid){
A[k++]=B[i++];
}
while(j<=high){
A[k++]=B[j++];
}
}
public 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(n),则算法的时间复杂度为O(nlog2 n)
空间复杂度:
O(n) (使用辅助数组O(n),递归占用空间O(log2 n),所以主要受数组占用空间影响)
稳定的算法
8.基数排序
将关键字拆分为d位
以个位、十位、百位…大小进行排序
基数排序通常基于链式存储实现
r是基数(比如0~10)
需要r个辅助队列,空间复杂度为O®
一趟分配O(n),一趟收集O®,总共d趟分配,收集,时间复杂度O(d(n+r))
稳定的算法
9.外部排序
数据元素太多,无法一次全部读入内存进行排序
外部(外存,磁盘)