堆(优先级队列)
队列是一种先进先出(FIFO)的数据结构,但有些情况下,操作的数据可能带有优先级,一般出队
列时,可能需要优先级高的元素先出队列,该中场景下,使用队列显然不合适,比如:在手机上玩游戏的时候,如
果有来电,那么系统应该优先处理打进来的电话;初中那会班主任排座位时可能会让成绩好的同学先挑座位。
在这种情况下,数据结构应该提供两个最基本的操作,一个是返回最高优先级对象,一个是添加新的对象。这种数
据结构就是优先级队列
堆:
如果有一个关键码的集合**K = {k0,k1, k2,…,kn-1},把它的所有元素按完全二叉树的顺序存储方式存储 在一个一维数组中,并满足:Ki <= K2i+1** 且 Ki<= K2i+2 (Ki >= K2i+1 且 Ki >= K2i+2) i = 0,1,2…,则称为 小堆(或大堆)。将根节点最大的堆叫做最大堆或大根堆,根节点最小的堆叫做最小堆或小根堆。
按照个人理解,就是一个顺序排列的完全二叉树.
根节点的值总是大于或者小于左右孩子的.它的每颗子树都要满足这个性质
为什么是完全二叉树?
如果不是完全二叉树,树的高度越高,浪费的空间越大.那样不如使用链式存储.
根节点从0开始:
-
已知孩子 求双亲节点. (i-1) /2;
-
已知双亲节点.求孩子节点: 如果: i *2 + 1 小于节点个数,左孩子: i *2 + 1;
-
如果: i *2 + 2小于节点个数,右孩子: i * 2 + 2;
堆的创建:
对于集合{ 27,15,19,18,28,34,65,49,25,37 }中的数据,如果将其创建成堆呢?
1:向下调整 适合一个无序数组
2:向上调整 适合一个一个插入
1:向下调整
27 | 15 | 19 | 18 | 28 | 34 | 65 | 49 | 25 | 37 |
---|---|---|---|---|---|---|---|---|---|
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
把这棵树调成大根堆: 1 先把最后一棵子树调成大根堆
之后依次调整每一棵子树
调整这棵树的时候,它的子树也要进行判断是不是大根堆.
堆为什么不从上往下调整?
从上往下调整不能确定一定是堆.万一最大的数字在最下面.一次没办法把他调整到最上面.
只要保证这棵树的子树都是堆,那么调整一次就能确定他依旧是堆.
2向上调整
适合往堆中插入元素进行建堆
代码:
### 代码:
```
public class TestHeap {
//堆的实质就是一个顺序存储的完全二叉树
public int[] elem; //顺序表
public int usedSize;//存储的元素个数
public TestHeap() {
this.elem = new int[10]; // 构造方法,初始化元素值默认10
}
public void initElem(int[] array) {
elem = Arrays.copyOf(array, array.length);
usedSize = array.length;
}//初始化这个数组,并没有创建出一个堆
//向下调整创建堆
public void createHeap() {
for (int parent = (usedSize - 1 - 1) / 2; parent >= 0; parent--) {
//usedSize-2: -1是因为下标 再-1是因为计算parent的值就是(i-1)/2
shitfDown(parent, usedSize);//每棵树向下调整
}
}
//向下调整
public void shitfDown(int parent, int usedSize) {
int child = 2 * parent + 1;
while (child < usedSize) { //最起码要有左孩子
if (child + 1 < usedSize && elem[child + 1] > elem[child]) {
child++;//判断左右孩子的最大值
}
if (elem[child] > elem[parent]) {
swap(elem, child, parent);//调整完一次 之后继续调整这棵树
parent = child;
child = 2 * parent + 1;
} else {
break;//既然这是个大根堆 那就不用调整了,因为大根堆的子堆一定是大根堆
}
}
}
public void swap(int[] elem, int child, int parent) {
int tmp = elem[child];
elem[child] = elem[parent];
elem[parent] = tmp;
}
//插入数据
public void offer(int key){
if(isFull()){
elem = Arrays.copyOf(elem,elem.length * 2);
} //判满 扩容
elem[usedSize++] = key; //放入元素
shitfUp(usedSize-1); //向上调整
}
public boolean isFull(){
return usedSize == elem.length;
}
}
向上调整:
private void shitfUp(int child){
int parent = (child -1) /2; //双亲节点等于下标-1 /2
while (child > 0){ //只要孩子节点大于0就一直循环下去
if (elem[child] > elem[parent]){
swap(elem,child,parent); //大根堆中孩子大于双亲就交换
child = parent;
parent =( child - 1) / 2; //堆的调整要更新child和parent的值
}else {
break;//大根堆的子堆一定是大根.
}
}
}
删除:
public void poll(){
if(isEmpty()){
return;
}
swap(elem,0,usedSize-1); //交换堆顶和最后一个元素
usedSize--; //有效元素减1
shitfDown(0,usedSize); //堆顶元素向下调整
}
向下调整建堆:时间复杂度O(N)
向上调整建堆时间复杂度O(N*logN)
接口:PriorityQueue: priorityQueue放置的元素必须可以比较大小
-
不能插入Null对象
-
接口默认是小根堆
-
插入和删除的时间复杂度是O(N*logN)
Queue<Integer> priorityQueue = new PriorityQueue<>(); //动态绑定 父类引用指向子类对象
调用不带参数默认构造方法 初始容量11 默认没有比较器
1TOP K问题
数据量太大,没办法进行比较.
取数组中最小的K个数:
先创建一个空数组
判断异常情况
建立小根堆 因为是Interger,自带比较器.不用重写比较器
将数组放入堆中.
弹出K次元素放入数组返回
但是这个是整体建堆,空间和时间浪费严重 O(NlogN)
优化:
时间复杂度:O(NlogK)
TOP K是 建立K个数组的堆
找最小的 建立大堆 找最大的 建立小堆
找最小的K个元素, 堆顶就是前K个元素 中最大的.数组元素和他比较,比他小说明堆顶不是最小的K个元素
找K个最大的元素.堆顶就是K个最大的值中最小的.当遍历到数组元素大于堆顶时,说明堆顶元素不是前K个最大值
比较器(Comparable和Comparator接口)
class student implements Comparable<student>{
public int age;
public String name;
@Override
public int compareTo(student o) {
return this.age = o.age;
}//重写comparator接口 但只能按照这种方法进行比较. 对类的侵蚀性强
//比较的是权重 >0说明后者权重大 则升序
<0说明后者权重小 则降序
}
class Namestudent implements Comparator<student>{
@Override
public int compare(student o1, student o2) {
return o1.name.compareTo(o2); //调用String类的comparaTo方法
}
}
堆排序
1建堆
-
小根堆
-
大根堆
将一组数据从小到大排序:使用大根堆.
如果使用小根堆左右孩子不能保证谁打谁小.
public static void main(String[] args) {
TestHeap heap = new TestHeap();
int[] nums = {27,15,19,18,28,34,65,49,25,37};
heap.initElem(nums);//初始化这个数组
heap.createHeap();//建立一个大根堆
}
public void heapSort(){
int end = usedSize-1; //记录下标
while (end > 0){
swap(elem,0,end);//交换堆顶和最后一个元素
shitfDown(0,end);堆顶元素向下调整
end--;//除去排好序的元素
}
}