堆排序
思想:
堆是一棵完全二叉树,可以用数组来存储,假设某元素在数组中下标为i,如果它有左子树,那么左节点的下标是2*i+1;
如果有右子树,右节点的下标是2*i+2
如果有父节点,父节点的下标是(i-1)/2取整。
堆分为最大堆和最小堆,最大堆的任意子树跟节点不小于任意子节点,最小堆的根节点不大于任意子节点
堆排序就是利用堆来对数组排序,我们使用的是最大堆
处理思想和冒泡、选择排序有些类似,每次找到最大的那个。最大堆的最大元素一定在0位置,构建好堆之后,0位置元素和末尾元素交换,最后节点即是序列最大值,然后用调整最大堆的方法调整剩下的堆,使之符合堆的性质,如此下去直至所有元素遍历完成
堆排序步骤:
1.构建最大堆
2.从数组末尾与0位置元素交换,成为新的顶
3.新的堆可能不满足最大堆的性质,要调用maxHeap调整堆
堆排序为原位排序
时间复杂度:
最大堆的调整:每次得到最大堆的过程是用父节点和左右子节点比较,把最大值的点放在父节点的位置,所以最大堆的一次调整就是在一层一层的比较,如果该层的父节点小于子节点,就调整该节点,每层的调整时间是常数级别,共有log2n+1层,所以最大堆调整的时间复杂度是O(logn)
[具有n个节点的完全二叉树的深度为log2n+1]
如果按平常的思维,最大堆调整是logn,对n个节点调整,则建堆要O(nlogn),!!但是不是的,具体推理以后再看,要记住!!
建堆:O(n)
堆排序:堆排序是用0位置元素和最后一个节点交换,交换之后对剩下的堆进行最大堆的调整,每次更新最大堆的过程是logk,这里的k是堆的大小,有n个元素所以时间复杂度是O(nlogk),如果对全体n排序要得到所有的顺序,就要建一个n个元素的堆,如果只需要前k个,那么只建一个k个元素的堆就可以
根元素为最小值的二叉堆:
插入节点时间复杂度为O(log n)
删除节点时间复杂度为O(log n)
查询最小元素的复杂度是o(1)
合并两个堆的复杂度是o(lgn)
因为建堆的时间复杂度是O(n)注意不是O(nlgn),所以合并两个普通的堆时间复杂度最多O(n),
但是采用左式堆等特殊的堆可以使合并复杂度变成 O(lgn)
查询最小元素的复杂度是O(1),删除最小堆的根节点后需要调整,复杂度与树高有关,O(lgn);
public class HeapSort {
//堆排序
public void heapSort(int[] arr){
if(arr==null||arr.length<=1)
return;
//构建最大堆
buildMaxHeap(arr);
//把末尾的拿出来和堆顶交换,这个数不一定是当前最大值,所以用maxHeap对0~i-1再保持堆的特性
//相当于每次建堆求出最大值,把它放到最后不管了,再排其他的
for(int i=arr.length-1;i>=0;i--){
swap(arr,i,0);
maxHeap(arr,i,0);
}
}
public void buildMaxHeap(int[] arr){
if(arr==null||arr.length<=1)
return;
int half=arr.length/2;//half后面的都是叶节点了
for(int i=half;i>=0;i--){
maxHeap(arr,arr.length,i);
}
}
public void maxHeap(int[] arr,int heapSize,int root){
int left=2*root+1;
int right=2*root+2;
int largest=root;
if(left<heapSize&&arr[left]>arr[root]){
largest=left;
}
if(right<heapSize&&arr[right]>arr[largest]){
largest=right;
}
if(root!=largest){
swap(arr,root,largest);
maxHeap(arr,heapSize,largest);
}
}
public void swap(int[] a,int m,int n){
int temp=a[m];
a[m]=a[n];
a[n]=temp;
}
}
之后再看一下,只维持k大小的堆,求很多数中的前k个