点击上方蓝字,关注并星标,和我一起学技术.
堆结构
学过数据结构我们知道完全二叉树的结构如下
我们可以把一个数组通过某种对应关系把数组映射成一颗完全二叉树的.下标的映射关系如下:
- 节点的左孩子:, 节点的右孩子为
- 节点的父节点为:
所以本质上是不存在二叉树结构, 实际上就是把数组的下标通过这种映射为完成二叉树结构.
堆结构就是一颗完全二叉树, 大根堆是这样一个完全二叉树, 完全二叉树中任何一颗子树的最大值都是这颗子树的头部. 小根堆同理.
![388967f86584242322220b3782d0684b.png](https://i-blog.csdnimg.cn/blog_migrate/9318896821e60bfa07fc576ba11f9efe.png)
那么问题来了怎么把一个数组映射成一个堆结构?0到 i - 1 的位置已经是映射好的大根堆了, 现在新加进来i位置的数, 只要稍微调整一下 i 位置与父节点的关系即可. 只要i位置的值比父节点的值要大, 就一直向上调.
public static void heapInsert(int[] arr, int index){
while (arr[index] > arr[(index - 1) / 2]){
swap(arr, index, (index - 1) / 2);
index = (index - 1) / 2;
}
}
建立大根堆的过程第一是遍历一遍数组, 第二与完全二叉树的节点进行比较向上调整这只与高度有关, 所以总的时间复杂度为的总和.
为的时间复杂度.
第二个问题来了, 如果一个数组已经调整成为了一个大根堆, 但是数组中某个元素发生改变后怎么继续维持大根堆结构?
![3d39e1afb112a011fbe3d51f9c1be0b8.png](https://i-blog.csdnimg.cn/blog_migrate/c0a536a282d65ba6646f110c1985dfdc.png)
如图6节点的值变为1, 那么变化为就不是一个大根堆了,这时候需要调整. 这个时候就是向下沉的过程, 比较1的左右孩子的最大值, 与最大值进行交换, 重复这个过程. 这时间复杂度显然只有
// 0 - heapSize - 1 的位置已经形成了大根堆. 当index位置的数变小后, 这个节点下沉的过程.
public static void heapIfy(int[] arr, int index, int heapSize){
int left = index * 2 + 1;
int right = index * 2 + 2;
while (left // 若left > heapSize, 则说明这个是叶节点无需处理.
// 下面这句是找到左右子树的最大值
int largest = right arr[left]
? right
: left;
// 下面这句是比较,当前index的值与子树最大值比较
largest = arr[largest] > arr[index] ? largest : index;
// 如果index的值是最大的就无需调整
if (largest == index)
break;
// index!=largest 就需要与子树最大值节点交换, 继续进行while循环
swap(arr, largest, index);
index = largest;
left = index*2 + 1;
}
}
我们总结一下,
- heapInsert过程是一个形成好的堆结构,新加进来一个数即最后的叶节点,这个叶节点向上调形成一个堆的过程.
- heapIfy过程是一个形成好的堆结构,其中一个节点变化后,向下沉的形成一个堆的过程.
那么这个堆结构用什么用处呢?
我们不妨看一道题一个不断吐出不同数的机器, 我们需要不定时的求出吐出的所有数的中位数.
传统的做法就是先用一个容器去不断的接受这个数, 当需要中位数的时候就将数组排序一遍然后取中位值. 然而这个方法存在严重的问题, 每次新的数加进来后,如果又要取中位数那么就需要再次排序再取,这样时间的代价是非常高的.
我们考虑用堆排序这个结构解决这个问题.
用一个大根堆与一个小根堆, 在不断接受新的数的过程中始终保持大根堆与小根堆的数量一致各占一半. 较小的数放在大根堆,较大的数放在小根堆. 这样取中位数就能直接取得.
堆结构是极其重要的数据结构,很逆天,调整数的过程的时间复杂度就是, 如果有40多亿的数据,只需要比较调整32次. 所以这也是笔试面试的重点.
堆排序
堆排序就是利用堆结构进行排序.
将数组先弄成大根堆.
然后将最大的数6与数组最后的元素交换. 这样最大的一个数就完成排序.
将6从堆中踢除. 堆顶元素3进行heapIfy的过程,继续弄成一个大根堆.
然后继续上面的过程, 直到所有数排序好.
public class Sort {
public static void swap( int[] arr, int index1, int index2 ){
int temp = arr[index1];
arr[index1] = arr[index2];
arr[index2] = temp;
}
public static void heapInsert(int[] arr, int index){
while (arr[index] > arr[(index - 1) / 2]){
swap(arr, index, (index - 1) / 2);
index = (index - 1) / 2;
}
}
// 0 - heapSize - 1 的位置已经形成了大根堆. 当index位置的数变小后, 这个节点下沉的过程.
public static void heapify(int[] arr, int index, int heapSize){
int left = index * 2 + 1;
int right = index * 2 + 2;
while (left // 若left > heapSize, 则说明这个是叶节点无需处理.
// 下面这句是找到左右子树的最大值
int largest = right arr[left]
? right
: left;
// 下面这句是比较,当前index的值与子树最大值比较
largest = arr[largest] > arr[index] ? largest : index;
// 如果index的值是最大的就无需调整
if (largest == index)
break;
// index!=largest 就需要与子树最大值节点交换, 继续进行while循环
swap(arr, largest, index);
index = largest;
left = index*2 + 1;
right = index*2 + 2;
}
}
public static void heapSort(int[] arr){
if (arr == null || arr.length 2){
return;
}
// 建立大根堆的过程
for (int i = 0; i heapInsert(arr, i);
}
int heapSize = arr.length;
// 堆中的最大数与最后一个数交换, 交换后堆的大小减1
swap(arr, 0, --heapSize);
while ( heapSize > 0){
heapify(arr, 0, heapSize); // 继续调成一个大根堆
swap(arr, 0, --heapSize);
}
}
}
时间复杂度为, 空间复杂度为
堆结构非常重要:
- 堆结构的heapInsert与heapify过程
- 堆结构的增大与减少
- 如果只是建立堆的过程,时间复杂度为
- 优先级队列就是堆结构
![4fe71893d54708673434743852305faa.png](https://i-blog.csdnimg.cn/blog_migrate/4a28bc69ff5395ce017d402c0a1862fe.png)