文章收录在公众号:bigsai 关注持续分享干货和资源java
前言
事情还要从一个故事讲起:
算法




对于上面那只可爱的小狗狗不会,本篇即为该教程,首先,我要告诉这只可爱的小狗狗,这种问题你要使用的数据结构为优先队列,每次操做的时间复杂度为O(logn)
,而整个过程的时间复杂度为O(nlogn)
.api
对于本片的设计与实现和堆排序可能有些类似,由于他们都借助堆来实现算法和数据结构,下面详细介绍优先队列的设计与实现。数组
堆
而堆就是一类特殊的数据结构的统称。堆一般是一个能够被看作一棵树(彻底)的数组对象。且老是知足如下规则:微信
堆老是一棵彻底二叉树数据结构
每一个节点老是大于(或小于)它的孩子节点。函数
对于彻底二叉树,我想你们都能明白,就是最底层叶子节点要严格按照从左向右来。
学习

堆有大根堆和小根堆,若是是全部父节点都大于子节点的时候,那么这就是个大根堆,反之则为小根堆,如下就是一个大根堆:
测试

最后须要注意的是咱们并非用链式去储存这个二叉树而是用数组去存储这个树,虽然链式的使用场景可能更多一些,可是在彻底二叉树的状况下空间使用率较好没有斜树的出现。而且在操做的时候能够直接经过编号找到位置进行交换。this
优先队列
如何理解优先队列,咱们先从一段对话提及:

优先队列,它是一个队列。而普通的队列听从先进先出的规则。而优先队列遵循一个排序的规则:每次抛出自定义排序最大(小)的,默认的状况是抛出最小的,本篇也就从最基本的原理进行分析。
而且它的用法队列仍是同样的,,因此咱们在设计这个类的时候api方面要与队列的api一致。

咱们主要实现add、poll、和peek方法,而且会着重于算法的实现而不太着重一些细节的实现。
虽然优先队列和堆排序利用堆结构特性的流程有一些类似,可是二者其实仍是有些操做上的区别的:
堆排序 :
刚开始是一个杂乱无章的序列,因此须要将杂乱的序列(树)经过一个方法变成一个合法的堆。
转成一个堆以后须要删除n次每次删除完都要从新调整这个堆。没有插入操做。
优先队列:
队列(堆)刚开始的内容为空,每次增长一个元素时须要及时调整堆。每次删除也要及时调整堆,增长和删除每次都只是一个元素。
可是优先队列的具体操做流程是如何的呢?咱们具体分析其插入和删除的流程。
插入add流程(小根堆为例):
正常处理完的优先队列内的数据知足一个堆的结构,因此就是插入在堆中。
堆是一棵彻底二叉树,因此在插入初始,插入到最后一个位置不影响其余结构。
节点和父节点比较大小(父节点索引为其二分之一)。若是该节点比父节点更小,则交换数据,一直到不能交换为止,这个过程不用担忧不合法,由于父节点更小的话更知足比孩子节点更小。

删除pop流程(小根堆为例):
pop删除操做取优先队列内最小的元素,而这个元素确定就是堆顶元素了,取完以后,这个堆的其余部分仍是知足堆的结构可是缺乏堆顶。
为了避免影响整个结构,咱们将末尾的那个元素移到堆顶,此时堆须要调整使其知足堆的性质条件。
交换的这个节点和左右孩子进行比较,若是须要交换则交换,交换后再次考虑交换子节点是否须要交换,一直到不交换为止。最坏状况是交换到根节点,这个复杂度每次为O(logn).

代码实现
我想到这里,优先队列的内部流程思想你已经掌握了,可是懂优先队列原理和手写优先队列是两个概念,为了更深刻的学习优先队列,在这里就带你手写一个简易型的优先队列。
在代码的具体实现方面,最主要的就是pop()和add()两个函数了。在pop()函数具体实现的时候,将最后一个元素移到堆头考虑和其余孩子节点交换的时候,用while进行操做的时候计算孩子下标的时候要确保不越界。咱们用的是数组存储数据,优先队列的长度不必定等于这个数组的长度。
而在实现add()函数的时候,这里简单的考虑了一下扩容。
具体实现的代码为:
import java.util.Arrays;
public class priQueue {
private int size;//优先队列的大小
private int capacity;//数组的容量
private int value[];//储存的值
public priQueue() {
this.capacity = 10;
this.value = new int[capacity];
this.size=0;
}
public priQueue(int capacity) {
this.capacity = capacity;
this.value = new int[capacity];
this.size=0;
}
/**
* 插入元素
* @param number
*/
public void add(int number) {
if(size==capacity)//扩容
{
capacity*=1.5;
value= Arrays.copyOf(value,capacity);
}
value[size++]=number;//先加到末尾
int index=size-1;
while (index>=1) {//进行交换
if(value[index]<value[index/2]) {
swap(index,index/2,value);
index=index/2;
}
else//不须要交换即中止
break;
}
}
public int peek() {
return value[0];
}
/**
* 抛出队头
* @return
*/
public int pop() {
int val=value[0];//呆返回数据额
value[0]=value[--size];//将最后一个元素赋值在堆头
int index=0,leftChild=0,rightChild=0;
while (true)
{
leftChild=index*2+1;
rightChild=index*2+2;
if(leftChild>=size)//左孩子必须知足在条件内
break;
else if(rightChild<size&&value[rightChild]<value[index]&&value[rightChild]<value[leftChild])
{//右孩子更小
swap(index,rightChild,value);
index=rightChild;
}
else if(value[leftChild]<value[index])
{//左孩子更小
swap(index,leftChild,value);
index=leftChild;
}
else //不须要 它本身最小
break;
}
return val;
}
//交换两个元素
public void swap(int i,int j,int arr[]) {
int team=arr[i];
arr[i]=arr[j];
arr[j]=team;
}
public int size() {
return size;
}
}
写个类测试一下看看:

结语

本次优先队列介绍就到这里啦,感受不错记得点赞或一键三连哦,建议和堆排序一块儿看和学习效果更佳,要可以手写代码。我的公众号:bigsai
回复 bigsai 更多精彩和资源与你分享。

本文分享自微信公众号 - bigsai(bigsai)。
若有侵权,请联系 support@oschina.cn 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一块儿分享。