一.优先级队列
- 概念:队列是一种先进先出(FIFO)的数据结构,但有些情况下,操作的数据可能带有优先级,一般出队列时,可能需要优先级高的元素先出队列,该中场景下,使用队列显然不合适,比如:在手机上玩游戏的时候,如果有来电,那么系统应该优先处理打进来的电话。在这种情况下,我们的数据结构应该提供两个最基本的操作,一个是返回最高优先级对象,一个是添加新的对象。这种数据结构就是优先级队列(Priority Queue)。
import java.util.PriorityQueue;
public class TestDemo {
public static void main(String[] args) {
//优先级队列(最下面实现方法是堆),从小到大排列好的数据,
PriorityQueue priorityQueue = new PriorityQueue();
priorityQueue.offer(13);
priorityQueue.offer(3);
priorityQueue.offer(8);
priorityQueue.offer(49);
/*
本来按照队列的先进先出原则,队头元素是13,但是对于优先级队列来说,内部已经排好序,因此是3
*/
System.out.println(priorityQueue.peek());
System.out.println(priorityQueue.poll());//弹出队头3
System.out.println(priorityQueue.peek());//再次获取新队头就是8,这就是优先级队列
}
}
- 输出结果
二.堆
-
概念:如果有一个关键码的集合K = {k0,k1, k2,…,kn-1},把它的所有元素按完全二叉树的顺序存储方式存储 在一个一维数组中,并满足:Ki <= K2i+1 且 Ki<= K2i+2 (Ki >= K2i+1 且 Ki >= K2i+2) i = 0,1,2…,则称为小堆(或大堆)。将根节点最大的堆叫做最大堆或大根堆,根节点最小的堆叫做最小堆或小根堆。
-
特点:
1.堆总是一棵完全二叉树;
2.对于大根堆,任何一棵子树的根的值都不小于它 左右节点;3…对于小根堆,任何一棵子树的根的值都不大于它 左右节点;
-
堆的存储方式:从堆的概念可知,堆是一棵完全二叉树,因此可以层序的规则采用顺序的方式来高效存储。
-
重重重点(重要的事情说三遍)
1.如果i为0,则i表示的节点为根节点,否则i节点的双亲节点为 (i - 1)/2;
2.知道根节点下标为i,左孩子节点为2i+1(前提是存在);
3.右孩子节点为2i+2(前提是存在);
1.堆的构造方法(下面构造大根堆,小根堆同样的道理)
- 向下调整的方法
向下调整的意思就是,从每一棵子树的视角来看是从它的双亲节点往下调整的所以叫向下调整的;
//将数组先放入堆中,然后调用向下调整方法(adjustDown),构造堆
public void initHeap(int[] arr){
for (int i = 0;i < arr.length;i++){
elem[i] = arr[i];
usedSize++;
}
//建堆时间复杂度O(n*log2n),一棵子树完成后,j就减减,这样就能够找到下一棵子树的双亲下标了
for (int j = (usedSize-1-1)/2;j >= 0;j--){
adjustDown(j,usedSize);
}
}
//向下调整的方法构造堆(大根堆)(时间复杂度log2n)
//参数传入的parent是最后一棵子树双亲下标,len就是usedSize,堆中节点的个数
public void adjustDown(int parent,int len){
int child = 2*parent+1;//左孩子下标
//1.首先判断是不是有左孩子
while(child < len){
//是否有右孩子,如果有的话,child保存的是左右孩子中最大值的下标;
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 adjustUp(int child){
//知道孩子节点,根据孩子节点下标和双亲节点下标的关系,得到双亲节点下标
int parent = (child-1)/2;
/*
因为是向上走,因此下标肯定是从小到大,最后到根节点是0,
所以这里是大于0;child等于0;这个堆就已经走完了;
*/
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;
}
}
}
2.给堆添加新元素
- 思路:直接加在堆的最大后面,如下图添加,直接加在37的右节点位置出,然后调用向上调整或者向下调整,直接重新构造堆就可以了;
//模拟队列形式,给堆添加新元素
public void push(int val){
//1.首先判断该堆的存储大小是否已经满了,如果满了要进行扩容
if (isFull()){
this.elem = Arrays.copyOf(this.elem,elem.length*2);//空间扩展为原来的二倍
}
elem[usedSize] = val;//将新的值,直接加到堆的最后面
usedSize++;//进行加加,说明堆中元素又多了一个;
adjustUp(this.usedSize-1);//然后从孩子开始,向上进行调整
}
//判断堆是否已经满了
public boolean isFull(){
return this.usedSize == this.elem.length;
}
3.出堆
- 思路:将堆定元素和堆最后一个元素进行交换,然后弹出,并且将堆重新进行调整
//出堆(弹出堆顶元素)
public void pop(){
/*
思路:将堆定元素和堆最后一个元素进行交换,然后弹出,并且将堆重新进行调整
*/
//判断是否为空
if (isEmpty()){
return;
}
// 1.交换
int tmp = elem[0];
this.elem[0] = this.elem[this.usedSize-1];
this.elem[this.usedSize-1] = tmp;
this.usedSize--;//数据个数减少
//2.调用向下调整方法
adjustDown(0,usedSize);
System.out.println("=====");
}
//判断是否为空
public boolean isEmpty(){
return usedSize == 0;
}
}
完整代码
import java.util.Arrays;
public class MyHeap {
int[] elem;
int usedSize;
public MyHeap(){
this.elem = new int[10];
}
//向下调整的方法构造堆(大根堆)(时间复杂度log2n)
//向下调整的意思就是,从每一棵子树的视角来看是从它的双亲节点往下调整的所以叫向下调整的
//参数传入的parent是最后一棵子树双亲下标,len就是usedSize,堆中节点的个数
public void adjustDown(int parent,int len){
int child = 2*parent+1;//左孩子下标
//1.首先判断是不是有左孩子
while(child < len){
//是否有右孩子,如果有的话,child保存的是左右孩子中最大值的下标;
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;
}
}
}
//将数组先放入堆中,然后调用向下调整方法(adjustDown),构造堆
public void initHeap(int[] arr){
for (int i = 0;i < arr.length;i++){
elem[i] = arr[i];
usedSize++;
}
//建堆时间复杂度O(n*log2n),一棵子树完成后,j就减减,这样就能够找到下一棵子树的双亲下标了
for (int j = (usedSize-1-1)/2;j >= 0;j--){
adjustDown(j,usedSize);
}
}
//用向上调整的方法构造堆(向上调整就是从孩子节点向上,向双亲节点调整)
public void adjustUp(int child){
int parent = (child-1)/2;
/*
因为是向上走,因此下标肯定是从小到大,最后到根节点是0,所以这里是大于0;child等于0;这个堆就已经走完了
*/
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 push(int val){
//1.首先判断该堆的存储大小是否已经满了,如果满了要进行扩容
if (isFull()){
this.elem = Arrays.copyOf(this.elem,elem.length*2);//空间扩展为原来的二倍
}
elem[usedSize] = val;//将新的值,直接加到堆的最后面
usedSize++;//进行加加,说明堆中元素又多了一个;
adjustUp(this.usedSize-1);//然后从孩子开始,向上进行调整
}
//判断堆是否已经满了
public boolean isFull(){
return this.usedSize == this.elem.length;
}
//出堆(弹出堆顶元素)
public void pop(){
/*
思路:将堆定元素和堆最后一个元素进行交换,然后弹出,并且将堆重新进行调整
*/
//判断是否为空
if (isEmpty()){
return;
}
// 1.交换
int tmp = elem[0];
this.elem[0] = this.elem[this.usedSize-1];
this.elem[this.usedSize-1] = tmp;
this.usedSize--;//数据个数减少
//2.调用向下调整方法
adjustDown(0,usedSize);
System.out.println("=====");
}
//判断是否为空
public boolean isEmpty(){
return usedSize == 0;
}
}
//测试类,用来给堆里面传递元素
public class TestDemo {
public static void main(String[] args) {
int[] arr = {
27, 15, 19, 18, 28, 34, 65, 49, 25, 37};
MyHeap heap = new MyHeap();
heap.initHeap(arr);
}
}