TopK问题
现实中我们通常把堆(一种二叉树)使用顺序结构的数组来存储,需要注意的是这里的堆和操作系统虚拟进程地址空间中的堆是两回事,一个是数据结构,一个是操作系统中管理内存的一块区域分段。
1 、堆的概念
- 堆中某个节点的值总是不大于或不小于其父节点的值;
- 堆总是一棵完全二叉树。
- 按照层序遍历的结果储存在数组中
- 堆的基本作用是快速找集合中的最值
2、堆的实现
2.1堆向下调整建大堆
前提:左右子树必须已经是一个堆,才能调整,即只有一个不满足堆的性质
//向下调整建大堆
private static void CreateBig(int[] arr, int index) {
//index就是要调整的parent
int max = index * 2 + 1;
/**
* 保证有右孩子,再比较
* 不是叶子结点,一定有左孩子,但不一定有右孩子
* (1)没有右孩子,左边大
* (2)有右孩子,左边大或者右边大
* */
//当有左孩子时执行以下
while (max < arr.length) {
//如果有右孩子,并且右孩子大于左孩子,改变max的值
if (max + 1 < arr.length && arr[max + 1] > arr[max]) {
max++;
}
//判断交换调整
//当parent小于孩子时交换,否则不执行
if (arr[index] < arr[max]) {
int t = arr[max];
arr[max] = arr[index];
arr[index] = t;
//改变index的max的值,使循环继续
index = max;
max = 2 * index + 1;
} else {
break;
}
}
}
2.2堆向下调整建小堆
//向下调整,建小堆
private static void CreateSmall(int[] arr, int index) {
//左孩子下标,代表 index 的最小值孩子的下标
int min = index * 2 + 1;
//1.有叶子结点
while (min < arr.length) {
//2.保证有右孩子的前提下,右孩子比较小
//其他情况min就是最小的,即左孩子小
if (min + 1 < arr.length && arr[min + 1] < arr[min]) {
min++;
}
//3.根小于该孩子结点则不执行
if (arr[index] <= arr[min]) {
break;
}
//4.否则的话交换
int t = arr[min];
arr[min] = arr[index];
arr[index] = t;
//以该结点为根,继续向下调整
index = min;
min = 2 * min + 1;
}
}
2.3向上调整建大堆
/* 向上调整为大堆 */
@Override
public void adjustUp(int[] array, int index) {
//index为插入的下标
//父亲结点
int parent = (index - 1) / 2;
//一直调整到堆顶
while (parent >= 0) {
if (item[index] > item[parent]) {
int t = item[index];
item[index] = item[parent];
item[parent] = t;
index = parent;
parent = (index - 1) / 2;
} else {
break;
}
}
}
2.4建堆
给出一个数组,逻辑上是一个二叉树,但是还不是堆,从倒数的第一个非叶子节点的子树开始调整,一直调整到根节点的树,就可以调整成堆。
时间复杂度为O(N*logN),实际是O(N)
private static void CreateHeap(int[] arr) {
//从最后一个非叶子结点的下标开始
for (int i = (arr.length - 1 - 1) / 2; i >= 0; i--) {
//不断地做向下调整
//CreateSmall(arr, i);
CreateBig(arr,i);
}
}
2.3堆的插入、查看、删除
public class IHeapImpl implements IHeap {
private int[] item;
private int userSize;
public IHeapImpl(int[] arr) {
this.item =Arrays.copyOf(arr,arr.length);
// 代表数组中被视为堆数据的个数
this.userSize = 0;
}
/* 1.堆的插入 */
//先插入一个数字到数组的尾上,长度++,再进行向上调整算法,直到满足堆。
@Override
public void add(int item) {
if (Full()) {
this.item = Arrays.copyOf(this.item, 2 * this.item.length + 1);
}
this.item[userSize] = item;
this.userSize++;
//加到最后一个了,所以要向上调整
adjustUp(this.item, this.userSize - 1);
}
/* 向上调整为大堆 */
@Override
public void adjustUp(int[] array, int index) {
//index为插入的下标
//父亲结点
int parent = (index - 1) / 2;
//一直调整到堆顶
while (parent >= 0) {
if (item[index] > item[parent]) {
int t = item[index];
item[index] = item[parent];
item[parent] = t;
index = parent;
parent = (index - 1) / 2;
} else {
break;
}
}
}
@Override
public int peek() {
if (isEmpty()) {
//不支持的异常
throw new UnsupportedOperationException("堆为空");
}
return this.item[0];
}
//返回堆顶元素,并该数据元素
//删除堆是删除堆顶的数据,将堆顶的数据根和最后一个数据一换,然后删除数组最后一个数据,再进行向下调整算法。
@Override
public int pop() {
if (isEmpty()) {
//抛出不支持的异常
throw new UnsupportedOperationException("堆为空");
}
//先保留堆顶元素
int old=this.item[0];
//先交换,把堆顶元素和最后一个元素交换,最后一个元素下标为usedSize-1
int t = this.item[0];
this.item[0] = this.item[this.userSize - 1];
this.item[this.userSize - 1] = t;
//完了之后将交换后的最后一个元素删除,只要userSize--,说明下次做向下调整的时候就不走这里了
this.userSize--;
//此时只有0位值不满足堆的性质,向下调整建大堆
adjustDown(this.userSize , 0);
return old;
}
//向下调整
@Override
public void adjustDown(int len, int index) {
int max = index * 2 + 1;
while (max < len) {
if (max + 1 < len && this.item[max] < this.item[max + 1]) {
max++;
}
if (item[index] >= item[max]) {
break;
}
int t = item[index];
item[index] = item[max];
item[max] = t;
index = max;
max = index * 2 + 1;
}
}
//创建大根堆
@Override
public void createHeap(int[] array) {
this.userSize=array.length;
for (int i = (array.length - 1 - 1) / 2; i >= 0; i--) {
adjustDown(this.userSize, i);
}
}
@Override
public void show() {
for (int i = 0; i < this.userSize; i++) {
System.out.print(item[i] + " ");
}
}
@Override
public boolean Full() {
return this.userSize == this.item.length;
}
@Override
public boolean isEmpty() {
return this.userSize == 0;
}
}