目录
6.堆排序
学习堆排序就不得不介绍一下二叉树的种类,二叉树分为完全二叉树、均衡二叉树、有序二叉树、满二叉树、完美二叉树。可以认为堆排序是利用完全二叉树的结构设计的排序算法。完全二叉树的序号与数组对应如下
算法步骤
1. 先形成大根堆,根节点的值大于子节点;
2. 然后将序列中的第一个数与最后一个数交换,最后一个数会是最大值,排序序列长度减1,堆长减1;
3. 重复1,3直到排序序列为1。
大根堆形成思路:插入上升法,遍历每个元素与其父节点比较。时间复杂度O(nlog(n))。
heapify操作,借助原有大根堆的特性,顶端下降法,时间复杂度O(logn)。
算法总体时间复杂度O(nlog(n)),空间复杂度O(1)。不稳定。
```Java
public static int[] sortHeap(int[] a){
int len=a.length;
//得到大根堆
MaxHeap(a,len);
//交换一次,堆长度减1
for(int i=len-1;i>0;i--){
swap(a,0,i);
len--;
heapify(a,0,len);
}
return a;
}
//这里得通过顶端下降法,借助第一次生成大根堆的特性 复杂度log(n)
private static void heapify(int[] a, int i, int len) {
int left=2*i+1;
int right=2*i+2;
while (left<len){
int largeIndex;
//比较子节点的大小,右节点为空的时候,左节点最大
if(right<len&&a[left]<a[right]){
largeIndex=right;
}else{
largeIndex=left;
}
//与根节点比较
if(a[i]<a[largeIndex]){
swap(a,i,largeIndex);
i=largeIndex;
}else {
break;
}
left=2*i+1;
right=2*i+2;
}
}
private static void swap(int[] a, int i, int j) {
int temp=a[i];
a[i]=a[j];
a[j]=temp;
}
//插入上升法复杂度nlog(n)
private static void MaxHeap(int[] a, int length) {
for(int i=0;i<length;i++){
int index=i;
while (a[index]>a[(index-1)/2]){
swap(a,index,(index-1)/2);
index=(index-1)/2;
}
}
}
```
7.快速排序
快速排序使用分治策略把一个串行分为两个子串行,本质上看来是在冒泡排序的基础上递归分治法,与冒泡相比交换的间隔变大了,速度就变快了。接下来我将介绍快排的三种版本,分别称为1.0版本、2.0版本和3.0版本。其中快排的核心是partition操作。
partition:将一个序列划成3部分,小于等于某个数放在左边,大于放在右边,等于放在中间,这是著名的荷兰国旗问题。要求额外空间复杂度为O(1),时间复杂度为O(n)。
算法步骤:
1. 选择序列的最后一个数作为划分值,定义2个数作为小于区的左边界,大于区的右边界;
2. 从第一个开始数遍历,小于等于划分值与小于区下一个数交换,小于区右扩,大于划分值和大于区前一个数交换,大于区右扩。等于划分值,遍历下一个数;
3. 划分值与大于区的第一个数交换。
```Java
public static int[] partition(int[]a,int L,int R){//L、R为0和length-1
int less=L-1;//小于区右边界
int more=R;//大于区左边界
while(L<more){
if(a[L]<a[R]){
swap(a,++less,L++);
}else if(a[L]>a[R]){
swap(a,--more,L);
}
else if(a[L]==a[R]){
L++;
}
}
swap(a,more,R);
//返回交换后划分值的位置以及小于区下一个数据的位置,中间区
return new int[]{less++,more};
}
```
7.1快排1.0
算法步骤:
1. 对无序序列进行partition操作;
2. 然后在小于等于区和大于区分别进行partition操作。
```Java
public static int[] sortQuick1(int[] a,int L,int R){
if(L>=R){
return a;
}
int[] b=partition(a, L, R);
sortQuick1(a,L,b[1]-1);
sortQuick1(a,b[1]+1,R);
return a;
}
```
7.2快排2.0
与快排1.0相比,算法步骤2变成在小于区和等于区进行partition操作。速度比1.0快。
```Java
public static int[] sortQuick2(int[] a,int L,int R){
if(L>=R){
return a;
}
int[] b=partition(a, L, R);
sortQuick2(a,L,b[0]-1);
sortQuick2(a,b[1]+1,R);
return a;
}
```
7.3快排3.0
快排1.0和快排2.0最坏的时间复杂度达到了(划分值为最大值或最小值)O(n2),快排3.0随机划分值进行partition操作。
```Java
public static int[] sortQuick3(int[] a,int L,int R){
if(L>=R){
return a;
}
//随机产生划分值与最后一个数交换
swap(a,L+(int)(Math.random()*(R-L+1)),R);
int[] b=partition(a, L, R);
sortQuick3(a,L,b[0]-1);
sortQuick3(a,b[1]+1,R);
return a;
}
```
7.4总结
1. 快排1.0和快排2.0时间复杂度O(n2);
2. 快排3.0时间复杂度O(logn);
3. 空间复杂度都是O(logn);
4. 不稳定
5. 排序要想跑的快就用快排,平均复杂度低;
6. 要想空间少就用堆排;
7. 要想有稳定性就选择归并排序;
8. 基于比较的排序不能做到时间复杂度O(n);
时间复杂度 | 空间复杂度 | 稳定性 | |
选择 | N2 | 1 | 不稳定 |
冒泡 | N2 | 1 | 稳定 |
归并 | NlogN | N | 稳定 |
插入 | N2 | 1 | 稳定 |
快排 | NlogN | logN | 不稳定 |
堆排 | NlogN | 1 | 不稳定 |