什么是优先队列
普通队列
先进先出
优先队列
给一个优先级,根据优先级可以优先出队,优先队列底层是可以使用堆实现
堆的相关概念
堆可以被看成一颗树的数组对象,满足如下条件:
1、总是一颗完全二叉树
2、父亲结点的优先级高于或低于左右孩子结点
满二叉树
1、所有叶子结点全在最后一层
2、所有非叶子结点都有左子树和右子树
3、叶子结点个数为 2^(h-1)
4、非叶子结点个数为 2^(h-1) -1
5、每一层的结点个数 2^(layer - 1) layer 层数
6、结点总个数为2(h-1)-1
完全二叉树
满二叉树肯定是完全二叉树,而完全二叉树不一定是满二叉树。
完全二叉树具有如下特点:
1、按照树的结构从左到右依次排序
2、叶子结点只能出现在最下层和次下层,且最下层的叶子结点集中在树的左部。
根据堆的性质可以得出如下结论
1、根结点没有父亲结点
2、除根结点之外的任意结点(i)的父亲结点的索引为: parent = i / 2
3、任意结点的左孩子结点的索引为:leftIndex = 2 * i
4、任意结点的右孩子结点的索引为:rightIndex = 2 * i + 1
上面的结论是根结点存储在索引为1的位置,如果根结点存储在索引为0的位置时,会得到:
parent = (i - 1) / 2;
leftIndex = 2 * i + 1
rightIndex = 2 * I + 2;
定义一个堆
private T[] data;//保存堆中的数据
private int size;//堆中元素的个数
public MyHeap(){
this.size = 0;
this.data = (T[]) new Comparable[100];
}
public MyHeap(T[] array){
this.data = Arrays.copyOf(array,this.size);
this.size = this.data.length;
}
//判断最大堆是否为空
public boolean isEmpty(){
return this.size==0;
}
//获取最大堆中元素的个数
public int getSize(){
return this.size;
}
堆的相关操作
获取父亲结点索引
//获取父亲结点的索引
private int getParentIndex(int index){
if (index<0){
throw new IllegalArgumentException("index id invdid");
}else if (index == 0){
return -1;
}
return (index-1)/2;
}
获取左孩子结点的索引
右孩子索引为左孩子加一
//根据索引获取左孩子结点的索引
private int getLeftChildIndex(int index){
if (index<0){
throw new IllegalArgumentException("index id invdid");
}
return 2*index+1;
}
交换操作
private void swap(T[] data, int curindex, int paraentIndex) {
T temp = data[curindex];
data[curindex] = data[paraentIndex];
data[paraentIndex] = temp;
}
向堆中添加元素
1、添加元素到堆的末尾,更新size
2、从最后一个结点开始与父亲结点进行(优先级)比较,如果父亲结点的优先级低于当前结点,则进行交换
3、重复第二步操作
4、直至根结点或父亲结点的优先级高于当前结点
/**
* 添加操作
* @param ele 元素
*/
public void add(T ele){
this.data[size] = ele;
//更新size
size++;
//上浮操作
//floatUp1(size-1);
floatUp2(size-1,ele);
}
floatUp1是使用交换操作,每次判断完是否交换都进行交换
private void floatUp1(int index) {
int curindex = index;
int paraentIndex = getParentIndex(curindex);
while (curindex>0 && this.data[paraentIndex].compareTo(this.data[curindex])<0){
swap(this.data,curindex,paraentIndex);
curindex = paraentIndex;
paraentIndex = getParentIndex(curindex);
}
}
floatUp2是不进行交换,使用值记录插入元素,比较完在条件符合时进行父元素覆盖子元素操作,最后一次将插入值插入到最后一次的当前结点
private void floatUp2(int index, T ele) {
T curdata = ele;
int curindex = index;
int parentIndex = getParentIndex(curindex);
while (curindex>0 && this.data[parentIndex].compareTo(ele)<0){
data[curindex] = data[parentIndex];
curindex = parentIndex;
parentIndex = getParentIndex(curindex);
}
data[curindex] = curda、a;
}
取出堆中优先级最高的元素(下沉 swim)
最大堆中优先级最高的元素是索引为0的元素
1、使最后一个元素替换索引为0的元素,更新size
2、索引为0的位置开始进行下沉操作
下沉操作:
1.找到当前结点左右孩子结点中优先级较高的结点
2.如果当前结点的优先级小于左右孩子中优先级较高的结点,则进行交换。
3.重复第二步操作
3、直至叶子结点或左右孩子结点中优先级较高结点小于当前结点的优先级。
public T removePriorityFirst() {
if (isEmpty()) {
throw new IllegalArgumentException("heap is null!");
}
// 1、保存根元素
T result = this.data[0];
// 2、用最后一个元素替换根元素
this.data[0] = this.data[this.size - 1];
// 3、更新size
this.size -= 1;
// 4、swim操作
swim1();
//swim2();
return result;
}
private void swim2(T ele) {
if (isEmpty()){
return;
}
int curindex = 0;
int leftChildIndex = getLeftChildIndex(curindex);
int changeIndex = leftChildIndex;
while (leftChildIndex<this.size){
if (leftChildIndex+1 <size && data[leftChildIndex].compareTo(data[leftChildIndex+1])<0){
changeIndex = leftChildIndex+1;
}
if (ele.compareTo(data[changeIndex])>0){
break;
}
data[curindex] = data[changeIndex];
curindex = changeIndex;
leftChildIndex = getLeftChildIndex(curindex);
changeIndex = leftChildIndex;
}
data[curindex] = ele;
}
private void swim1() {
if (isEmpty()) {
return;
}
int curindex = 0;
int leftChildIndex = getLeftChildIndex(curindex);
int changeIndex = leftChildIndex;// 保存左右孩子优先级高的索引
while (leftChildIndex<this.size){
if (leftChildIndex+1<size && data[leftChildIndex].compareTo(data[leftChildIndex+1])<0){
changeIndex = leftChildIndex + 1;
}
if (data[curindex].compareTo(data[changeIndex])>0){
break;
}
swap(data,curindex,changeIndex);
curindex = leftChildIndex;
leftChildIndex = getLeftChildIndex(leftChildIndex);
changeIndex = leftChildIndex;
}
}
堆的时间复杂度分析
无论进行上浮还是下沉操作,最多交换的次数为整颗树的高度
O(h) = O(logn)
Heapify和Replace
replace :取出最大元素后,放入一个新元素
实现方式 :直接将堆顶元素替换成新元素,然后进行下沉(swim)操作
public void replace(T ele){
if (isEmpty()) {
data[0] = ele;
return;
}
data[0] = ele;
swim1();
}
Heapify :将任意数组整理成堆的形状
实现方式 :从最后一个元素的父亲结点开始进行整理(下沉操作 swim),直到根节点。
1、找到最后一个元素的父亲结点。 (size-1-1)/2
2、循环进行下沉操作,直至根结点
public void headify(T[] arr){
if (this.data == null || this.data.length == 0){
return;
}
int lastEleParentIndex = (this.data.length -1-1)/2;
for (;lastEleParentIndex >= 0;lastEleParentIndex--){
heapifySwim(this.data,lastEleParentIndex,this.data.length);
}
}
private void heapifySwim(T[] arr, int lastEleParentIndex, int length) {
int curIndex = lastEleParentIndex;
int leftIndex = getLeftChildIndex((curIndex));
int changeIndex = leftIndex;
while (leftIndex<length){
if (leftIndex + 1 < length && arr[leftIndex].compareTo(arr[leftIndex + 1]) < 0) {
changeIndex = leftIndex + 1;
}
if (arr[curIndex].compareTo(arr[changeIndex]) > 0) {
break;
}
swap(arr, curIndex, changeIndex);
curIndex = changeIndex;
leftIndex = getLeftChildIndex(curIndex);
changeIndex = leftIndex;
}
}
复杂度分析
从上面分析得知,讲一个元素插入到堆中,复杂度为O(logn),所以,将n个元素逐个插入到一个空堆中,算法复杂度为O(nlogn);
heapify的过程:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9p6vvG5c-1645084755542)(E:\Users\asus\AppData\Roaming\Typora\typora-user-images\1645082901581.png)]
所以时间复杂度为O(n)
堆排序
将一个数组传入到我们构建好的排序方法中,将数组元素依次入堆(add方法或者heapify方法),这样我们就可以获得一个大顶堆。
在依次取出堆顶元素放入数组中,就完成了堆排序。
// 堆排序
public void sort(T[] arr) {
if (arr == null || arr.length == 0) {
return;
}
// 构建堆
Arrays.stream(arr).forEach(item -> this.add(item));
// 依次删除根节点
int index = 0;
while (!isEmpty()) {
arr[index++] = this.removePriorityFirst();
}
}