文章目录
概要
队列是一种先进先出的数据结构,但有些情况下,操作的数据可能带有优先级,一般出队
列时,可能需要优先级高的元素先出队列,该中场景下,使用队列显然不合适。比如在一个队列的的几个元素中,按照以前我们要不从头出,要不从尾出,但是突然有一个需求,想要某一类的数据先出,此时就需要用到一个种特殊的数据结构(优先级队列)
这种数据结构提供两个最基本的操作,一个是返回最高优先级对象,一个是添加新的对象。
而为了实现优先出队列的基础逻辑,在其底层是一个堆这种数据结构。而堆实际上就是二叉树思想的拓展。
堆的概念
如果有一个关键码的集合K = {k0,k1, k2,…,kn-1},把它的所有元素按完全二叉树的顺序存储方式存储在一个一维数组中,并满足:Ki <= K2i+1且Ki<= K2i+2 (Ki >= K2i+1 且 Ki >= K2i+2) i = 0,1,2…,则称为小堆(或大堆)。将根节点最大的堆叫做最大堆或大根堆,根节点最小的堆叫做最小堆或小根堆。
性质:
- 堆中某个节点的值总是不大于或不小于其父节点的值
父亲结点的值<或 > 其字节点的值 - 堆总是一棵完全二叉树。
分类:
大根堆,小根堆
小根堆:
大根堆:
理论基础:
二叉树的其中有一个性质:
- 如果i为0,则i表示的节点为根节点,否则i节点的双亲节点为 (i - 1)/2
- 如果2 * i + 1 小于节点个数,则节点i的左孩子下标为2 * i + 1,否则没有左孩子
- 如果2 * i + 2 小于节点个数,则节点i的右孩子下标为2 * i + 2,否则没有右孩子
从1中的公式,我们在数组中就可以随便指定一个数组下标,然后找到他的父亲下标
所以以此我们就可以构建一个堆。这里我们构建一个小根堆
27,15,19,18,28,34,65,49,25,37,我们以此数组构建一个堆
- 让parent标记需要调整的节点,child标记parent的左孩子
- 如果parent的左孩子存在,即:child < size,进行以下操作,直到parent的左孩子不存在
parent右孩子是否存在,存在找到左右孩子中最小的孩子,让child进行标将parent与较小的孩子 child比较,如果:
1).parent小于较小的孩子child,调整结束
2).否则:交换parent与较小的孩子child,交换完成之后,parent中大的元素向下移动,可能导致子树不满足对的性质,因此需要继续向下调整,即parent = child;child = parent*2+1; 然后继续执行2。
public void shiftDown(int[] array, int parent) {
// child先标记parent的左孩子,因为parent可能右左没有右
int child = 2 * parent + 1;
int size = array.length;
while (child < size) {
// 如果右孩子存在,找到左右孩子中较小的孩子,用child进行标记
if(child+1 < size && array[child+1] < array[child]){
child += 1;
}
// 如果双亲比其最小的孩子还小,说明该结构已经满足堆的特性了
if (array[parent] <= array[child]) {
break;
}else{
// 将双亲与较小的孩子交换
int t = array[parent];
array[parent] = array[child];
array[child] = t;
// parent中大的元素往下移动,可能会造成子树不满足堆的性质,因此需要继续向下调整
parent = child;
child = parent * 2 + 1;
}
}
}
代码逻辑顺序视频(审核太慢了)明天上传
堆的建立
小根堆的建立
这个视频的首个图片随便找的,重点是内容.
堆的插入和删除
插入
堆的插入总共需要两个步骤:
- 先将元素放入到底层空间中
(空间不够时需要扩容) - 将最后新插入的节点向上调整,直到满足小根堆的性质
public void shiftUp(int child) {
// 找到child的双亲
int parent = (child - 1) / 2;
while (child > 0) {
// 如果双亲比孩子大,parent满足堆的性质,调整结束
if (array[parent] > array[child]) {
break;
}else{
// 将双亲与孩子节点进行交换
int t = array[parent];
array[parent] = array[child];
array[child] = t;
// 小的元素向下移动,可能到值子树不满足对的性质,因此需要继续向上调增
child = parent;
parent = (child - 1) / 1;
}
}
}
堆的删除
堆的删除一定删除的是堆顶元素。
为什么?(因为向下比较是最快的,时间复杂度上只在意堆的高度)
- 将堆顶元素对堆中最后一个元素交换
- 将堆中有效数据个数减少一个
- 对堆顶元素进行向下调整
这个删除的操作,其实就与队列弹出一个元素的操作类似,都是从数组中将元素删除。
PriorityQueue技术细节
实现
public class MypriorityQueue {
public int []elem;
public int usedSize;
public MypriorityQueue(){
this.elem=new int[10];
}
public void initElem(int [] array) {
for (int i = 0; i < array.length; i++) {
elem[i] = array[i];
usedSize++;
}
}
public void createHeap () {
//这里是传入的结点是一个三个数为基准的根节点
for (int parent = (usedSize - 1 - 1) / 2; parent >= 0 ; parent--) {
shiftDown(parent,usedSize);
}
}
//父亲小标
//每棵树的下标
private void shiftDown(int parent,int len){
int childe=2*parent+1;
//最起码要有左孩子(因为完全二叉树)
while (childe<len){
//如此便满足下标对应的最大值
if (childe+1<len&&elem[childe]<elem[childe+1]){
childe++;
}
if (elem[childe]>elem[parent]){
int tmp=elem[parent];
elem[parent]=elem[childe];
elem[childe]=tmp;
parent=childe;
childe=2*parent+1;
}else{
break;
}
}
}
//堆插入
private void shiftUp(int child){
int parent=(child-1)/2;
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 offer(int val){
if (isFull()){
elem= Arrays.copyOf(elem,elem.length*2);
}
elem[usedSize++]=val;
//向上调整
shiftUp(usedSize-1);
}
//是否满了
public boolean isFull(){
return usedSize==elem.length;
}
//弹出某个特定的元素
public void pop2(int val){
if (isEmpty()){
return;
}
int i = 0;
for ( ; i < usedSize-1; i++) {
if (elem[i]==val){
int tmp=elem[i];
elem[i]=elem[usedSize];
elem[usedSize]=tmp;
usedSize--;
break;
}
}
shiftDown(i,usedSize);
}
//弹出首个元素(类似与删除)
public void pop(){
if (isEmpty()){
return;
}
swap(elem,0,usedSize-1);
usedSize--;
shiftDown(0,usedSize);
}
//定义的排序
private void swap(int [] array,int i,int j ){
int tmp=array[i];
array[i]=array[j];
array[j]=tmp;
}
public boolean isEmpty(){
return usedSize==0;
}
//对数组进行排序,直至全部遍历完成
public void headShort(){
int end=usedSize-1;
while (end>0) {
swap(elem,0,end);
shiftDown(0,end);
end--;
}
}
}
Java集合框架中提供了PriorityQueue和PriorityBlockingQueue两种类型的优先级队列,
- PriorityQueue是线程不安全的
- PriorityBlockingQueue是线程安全的
(多线程会讲)这里放个链接:
使用细节:
- PriorityQueue中放置的元素必须要能够比较大小,不能插入无法比较大小的对象,否则会抛出ClassCastException异常(重要,刚开始使用的时候,我经常犯这种错误)
(这里使用最好结合一个比较器来判断,PriorityQueue在底层提供了一个比较的构造器)
什么是比较构造?请看我的这篇文章:(还没放链接,之后会放的) - 不能插入null对象,否则会抛出NullPointerException
- 没有容量限制,可以插入任意多个元素,其内部可以自动扩容
- PriorityQueue底层使用了堆数据结构
- PriorityQueue默认情况下是小堆—即每次获取到的元素都是最小的元素(简单而言就是对元素进行了排序)
小结
返回值 | 函数名 | 功能介绍 |
---|---|---|
boolean | offer(E e) | 插入元素e,插入成功返回true,如果e对象为空,抛出NullPointerException异常,时间复杂度O(log2(N)) ,注意:空间不够时候会进行扩容 |
E | peek() | 获取优先级最高的元素,如果优先级队列为空,返回null |
E | poll() | 移除优先级最高的元素并返回,如果优先级队列为空,返回null |
int | size() | 获取有效元素的个数 |
void | clear() | 清空 |
boolean | isEmpty() | 检测优先级队列是否为空,空返回true |
十一、其他文章接口
1.String方法(重要,对于操作字符串有巨大的帮助)
2.java常用的接口及其方法(包含拷贝,比较,排序,构造器)
3.初阶数据结构
3.1 顺序表:ArrayList
3.2 链表:LinkedList
3.3 栈:Stack
3.4 队列:Queue
3.5 二叉树:Tree
3.6 优先级队列:PriorityQueue(堆排序)
3.7 Map和Set
HashMap和HashSet,TreeMap和TreeSet
文章链接
4. 排序(7种方式)
4.1 插入排序(两种)
4.2 选择排序(两种)
4.3 快速排序
4.4 堆排序
里面有堆排序的实现和逻辑
文章链接