很久没写博客了 还是抽时间写一写~
堆用来做优先队列 插入与删除平均时间复杂度都为O(n),以链表实现插入是O(1),删除是O(n),数组实现反之
JUC包就有优先队列的实现(后面深入并发后再补充)
判断:叶子节点为左节点时是完全二叉树,叶子节点为右节点时,不连续紧密排列,非完全二叉树
堆的定义:
堆是一颗完全二叉树。堆的基本要求是堆中所有结点的值必须大于(或小于)其孩子结点的值。
完全二叉树:
若设二叉树的高度为h,除第 h 层外,其它各层 (1~h-1) 的结点数都达到最大个数,第 h 层有叶子结点,并且叶子结点都是从左到右依次排布。
堆是数据结构(以大根堆为例)
public class Heap {
// 容量
private int capbility;
// 元素大小
private int count;
// 元素数组
private int[] data;
Heap(int capbility) {
this.data = new int[capbility+1];
this.capbility = capbility;
this.count = 0;
}
}
注意 这里是capbility+1; 因为我们堆结点是从索引1开始,从1到capbility+1,data[0]是空
介绍两个性质
获取父结点 i/2
获取子结点 2*i 和 2*i+1
(如果是capbility则只是计算不同,获取父结点 (i-1)/2,子结点 2*i +1和 2*i+2 )
然后是插入操作
插入到是插入到最后的结点位置,容量++,为了保持堆的性质,开始上升shiftUp操作,不断与父结点比较,如果大于父结点则交换,到一个节点的时候或者满足堆的性质
public void insert(int item) {
// FIXME 可改为扩容操作
assert(count + 1 <= capbility);
count++;
data[count] = item;
shiftUp(count);
}
private void shiftUp(int k) {
while (k > 1 && data[k] > data[k/2])
{
// 与父结点交换
SortHelper.swap(data,k,k/2);
// 继续比较
k = k/2;
}
}
删除操作
把data[1](ps:data[0]为空)删除,容量--,为了保持堆的性质,把data[count]换到data[1],开始shiftDown操作,不断与子节点比较,与子节点中大的那个交换位置,到无子节点或者已经满足大于堆的性质
public int delete(){
assert( count > 0 );
int item = data[1];
data[1] = data[count];
count --;
shiftDown(1);
return item;
}
private void shiftDown(int k) {
// 如果有左节点
while (2*k <= count){
int j = 2*k;
// data[j] 是 data[2*k]和data[2*k+1]中的最大值
if (j+1 <= count && data[j] < data[j+1]) {
j = j+1;
}
// 与子节点最大的进行比较 如果大于子节点 就直接跳出
if( data[k] >= data[j] ) break;
// 交换位置和索引继续比较
SortHelper.swap(data,k,j);
k = j;
}
}
测试结果
堆排序
由上面得出结论 我们按照顺序每次把删除的元素就保存就是堆排序 是不是很简单~
public static void main(String[] args) {
int n = 10;
Heap heap = new Heap(n);
for (int i = 0; i < 10; i++) {
heap.insert((i+1)*2);
}
System.out.println("构建的堆:"+Arrays.toString(heap.getData()));
int[] arr = new int[n];
for (int i = 0; i < n ; i++) {
arr[i] = heap.delete();
}
System.out.println("排序后的数组:"+Arrays.toString(arr));
}
构建堆优化
其实构建堆不用每次都加一个元素就shiftUp操作
叶子节点一开始就一个 无需进行任何操作,所以我们从第一个非叶子节点进行,也就是22(索引5)和之前的元素进行shiftDown操作 ,
获取第一个非叶子节点:获取最后一个叶子节点(data[count])的父结点(data[count/2])
public void heapify(int[] arr, int n) {
for( int i = 0 ; i < n ; i ++ )
data[i+1] = arr[i];
count = n;
for (int i = n/2; i >= 1; i--) {
shiftDown(i);
}
}
注意我们这里是>= 1,因为我们从索引1开始计算
原地排序(不需要借助辅助空间)
把最大的data[0]一直往数组最后放,放完该排序的数组大小-1,再重复进行这个操作
public static void localSort(int[] arr,int n) {
heapify(arr, n);
for (int i = 0; i < n ; i++) {
SortHelper.swap(arr,0,n-(i+1));
__shiftDown(arr, n-(i+1), 0);
}
}
代码优化