目录
1. 堆的概念
1)堆逻辑上是一颗完全二叉树
2)堆物理上是保存在数组中
3)满足任意节点的值都大于其子树中节点的值,叫做大堆,或者大根堆。
4)满足任意节点的值都小于其子树中节点的值,叫做小堆,或者小根堆,或者最小堆。
2. 向下调整
前提:左右子树必须已经是一个堆,才能调整
说明:
1. array
代表存储堆的数组
2. size
代表数组中被视为堆数据的个数
3. index
代表要调整位置的下标
4. left
代表
index
左孩子下标
5. right
代表
index
右孩子下标
6. min
代表
index
的最小值孩子的下标
2.1 向下调整为大根堆
//向下调整
public void shiftDown(int parent, int len) {
int child = 2 * parent + 1;
while (child < len) {
if (child + 1 < len && elem[child] < elem[child + 1]) {
child++;
}
if (elem[child] > elem[parent]){
int tmp = elem[child];
elem[child] = elem[parent];
elem[parent] = tmp;
parent = child;
child = 2*parent+1;
}else {
break;
}
}
}
//大根堆
public void createHeap(int[] array) {
for (int i = 0; i < array.length; i++) {
elem[i] = array[i];
usedSize++;
}
for (int parent = (usedSize-1-1)/2; parent >= 0; parent--) {
shiftDown(parent, usedSize);
}
}
2.2 向下调整为小根堆
过程:
1. index
如果已经是叶子结点,则整个调整过程结束
1.
判断
index
位置有没有孩子
2.
因为堆是完全二叉树,没有左孩子就一定没有右孩子,所以判断是否有左孩子
3.
因为堆的存储结构是数组,所以判断是否有左孩子即判断左孩子下标是否越界,即
left >= size
越界
2.
确定
left
或
right
,谁是
index
的最小孩子
min
1.
如果右孩子不存在,则
min = left
2.
否则,比较
array[left]
和
array[right]
值得大小,选择小的为
min
3.
比较
array[index]
的值 和
array[min]
的值,如果
array[index] <= array[min]
,则满足堆的性质,调整结束
4.
否则,交换
array[index]
和
array[min]
的值
5.
然后因为
min
位置的堆的性质可能被破坏,所以把
min
视作
index
,向下重复以上过程
public static void shiftDown(int[] array, int size, int index) {
int left = 2 * index + 1;
while (left < size) {
int min = left;
int right = 2 * index + 2;
if (right < size) {
if (array[right] < array[left]) {
min = right;
}
}
if (array[index] <= array[min]) {
break;
}
int t = array[index];
array[index] = array[min];
array[min] = t;
index = min;
left = 2 * index + 1;
}
}
3. 堆的应用——优先级队列
3.1 概念
在很多应用中,我们通常需要按照优先级情况对待处理对象进行处理,比如首先处理优先级最高的对象,然后处理次 高的对象。最简单的一个例子就是,在手机上玩游戏的时候,如果有来电,那么系统应该优先处理打进来的电话。
在这种情况下,我们的数据结构应该提供两个最基本的操作,一个是返回最高优先级对象,一个是添加新的对象。这 种数据结构就是优先级队列(Priority Queue)
3.2 内部原理
优先级队列的实现方式有很多,但最常见的是使用堆来构建。
3.3 入队列
过程(以大堆为例):
1.
首先按尾插方式放入数组
2.
比较其和其双亲的值的大小,如果双亲的值大,则满足堆的性质,插入结束
3.
否则,交换其和双亲位置的值,重新进行
2
、
3
步骤
4.
直到根结点
//调整为大根堆
private void shiftUp(int child) {
int parent = (child-1) / 2;
while (child > 0) {
if (elem[child] > elem[parent]) {
int tmp = elem[child];
elem[child] = elem[parent];
elem[parent] = tmp;
child = parent;
parent = (child-1)/2;
}else {
break;
}
}
}
//入队列
public void offer(int val) {
if (isFull()) {
elem = Arrays.copyOf(elem,2*elem.length);
}
elem[usedSize++] = val;
shiftUp(usedSize-1);
}
//队列是否满
public boolean isFull() {
return usedSize == elem.length;
}
3.4 出队列(优先级最高)
以大根堆为例
为了防止破坏堆的结构,删除时并不是直接将堆顶元素删除,而是用数组的最后一个元素替换堆顶元素,然后通过向下调整方式重新调整成堆
1. 交换二叉树的第一个元素和最后一个元素
2.向下调整回大根堆
//向下调整
public void shiftDown(int parent, int len) {
int child = 2 * parent + 1;
while (child < len) {
if (child + 1 < len && elem[child] < elem[child + 1]) {
child++;
}
if (elem[child] > elem[parent]){
int tmp = elem[child];
elem[child] = elem[parent];
elem[parent] = tmp;
parent = child;
child = 2*parent+1;
}else {
break;
}
}
}
public int poll() {
if (isEmpty()) {
throw new RuntimeException("优先队列为空!");
}
int tmp = elem[0];
elem[0] = elem[usedSize - 1];
elem[usedSize-1] = tmp;
usedSize--;
shiftDown(0,usedSize);
return tmp;
}
public boolean isEmpty() {
return usedSize == 0;
}
3.4 Java中的优先级队列
PriorityQueue
implements Queue
错误处理
|
抛出异常
|
返回特殊值
|
入队列
|
add(e)
| offer(e) |
出队列
|
remove()
| poll() |
队首元素
|
element(0)
| peek() |
4. TopK 问题
1、如果求前K个最大的元素,要建一个小根堆。
2、如果求前K个最小的元素,要建一个大根堆。
3、第K大的元素。建一个小堆,堆顶元素就是第K大的元素。
4. 第K小的元素。堆顶元素就是第K小的元素。堆内的元素,就是前K个最小的元素。
前K个元素大小的代码示例
public class TopK {
/**
* 排序找前k个元素大小
* @param array
* @param k
* @return
*/
public static int[] topK(int[] array, int k) {
//1.创建一个大小为 K 的大根堆
PriorityQueue<Integer> maxHeap = new PriorityQueue<>(k, new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
return o1 - o2;
}
});
//2.遍历数组当中的元素,前K 个元素放到队列当中
for (int i = 0; i < array.length; i++) {
if (maxHeap.size() < k) {
maxHeap.offer(array[i]);
}else {
//3.从第K+1 个元素开始,每个元素和堆顶元素进行比较
int top = maxHeap.peek();
if (top > array[i]){
//先弹出
maxHeap.poll();
//后存入
maxHeap.offer(array[i]);
}
}
}
int[] tmp = new int[k];
for (int i =0; i < k; i++) {
tmp[i] = maxHeap.poll();
}
return tmp;
}
public static void main(String[] args) {
int[] array = {12,4,14,45,3,21};
int[] tmp = topK(array,3);
System.out.println(Arrays.toString(tmp));
}
}
5. 堆排序
将二叉树的元素从小到大在数组中排列
1、调整为大根堆
2、0下标和最后1个未排序的元素进行交换即可
3、end-- .
//利用大根堆从小到大排序
public void heapSort() {
int end = this.usedSize-1;
while (end > 0) {
int tmp = elem[0];
elem[0] = elem[end];
elem[end] = tmp;
shiftDown(0,end);
end--;
}
}
//向下调整
public void shiftDown(int parent, int len) {
int child = 2 * parent + 1;
while (child < len) {
if (child + 1 < len && elem[child] < elem[child + 1]) {
child++;
}
if (elem[child] > elem[parent]){
int tmp = elem[child];
elem[child] = elem[parent];
elem[parent] = tmp;
parent = child;
child = 2*parent+1;
}else {
break;
}
}
}