优先队列

普通的队列是一种先进先出的数据结构,元素在队列尾追加,而从队列头删除。在优先队列中,元素被赋予优先级。当访问元素时,具有最高优先级的元素最先删除。优先队列具有最高级先出 (first in, largest out)的行为特征。通常采用堆数据结构来实现。

​ 堆(英语:heap)是计算机科学中一类特殊的数据结构的统称。堆通常是一个可以被看做一棵树的数组对象。堆总是满足下列性质:

  • 堆中某个节点的值总是不大于或不小于其父节点的值;
  • 堆总是一棵完全二叉树。

堆的实现通过构造二叉堆(binary heap),实为二叉树的一种;由于其应用的普遍性,当不加限定时,均指该数据结构的这种实现。这种数据结构具有以下性质。

  • 任意节点小于(或大于)它的所有后裔,最小元(或最大元)在堆的根上(堆序性)。
  • 堆总是一棵完全树。即除了最底层,其他层的节点都被元素填满,且最底层尽可能地从左到右填入。

将根节点最大的堆叫做最大堆或大根堆,根节点最小的堆叫做最小堆或小根堆。

我们这里讲的是二叉堆。

堆的入队和出队的时间复杂度都是O(log n)



上图就是一个最大堆的事例

下面我们使用数组来构建一个最大堆,在这里为了便于理解,数组索引为0的节点不存放数值,从第二个节点开始存放数据。

当前节点的父节点、左孩子、右孩子的索引就会有如下的关系:

  • 父节点的索引:index/2 (index为当前节点的索引)
  • 左孩子的索引:index*2
  • 右孩子的索引:index*2+1

如果从数组的第一个节点开始存放数据的话,当前节点的父节点、左孩子、右孩子的索引就会有如下的关系:

  • 父节点的索引:(index-1)/2 (index为当前节点的索引)

  • 左孩子的索引:index*2+1

  • 右孩子的索引:index*2+2

最大堆的基础架构
import java.util.ArrayList;
public class MaxHeap<E extends Comparable<E>> { //这里使用数组来实现
private ArrayList<E> data; public MaxHeap(int capacity) {
data = new ArrayList<>(capacity);
} public MaxHeap() {
data = new ArrayList<>();
} //返回堆中元素的个数
public int getSize() {
return data.size();
} //判断堆是否为空
public boolean isEmpty() {
return data.isEmpty();
} //返回完全二叉树中索引为index的节点的父节点的索引
private int parent(int index) {
if (index == 0)
throw new IllegalArgumentException("index-0 doesn't have parent"); return (index - 1) / 2;
} //返回完全二叉树中索引为index的节点的左孩子的索引
private int leftChild(int index) {
return index * 2 + 1;
} //返回完全二叉树中索引为index的节点的右孩子的索引
private int rightChild(int index) {
return index * 2 + 2;
} //交换索引为i、j的值
private void swap(int i, int j) {
E t = data.get(i);
data.set(i, data.get(j));
data.set(j, t);
}
}
往堆中添加元素
  • 再向堆中添加元素时,除了要维持完全二叉树的结构,还要注意堆的约束条件:根节点的值要大于左右子树的值。

在这里因为我们使用数组来实现的堆,所以添加元素时,我们可以先将元素添加到数组的末尾,然后循环的与父节点比较大小,比父节点大就与父节点交换位置,之后就继续与新的父节点比较大小,直到小于等于父节点。

  • 如上图所示,我们要在这个堆中添加一个元素36

  • 先将元素添加到数组的末尾。

  • 然后通过当前的索引计算出父节点的索引,通过索引得到父节点的值16,通过比较新添加的节点比其父节点大,所以将新添加的值与父节点交换在数组中的位置。之后再与新的父节点41比较,36<41,结束操作。
添加元素的代码实现
//向堆中添加元素
public void add(E e) {
data.add(e);
siftUp(data.size() - 1);
} private void siftUp(int k) {
//k不能是根节点,并且k的值要比父节点的值大
while (k > 0 && data.get(parent(k)).compareTo(data.get(k)) < 0) {
swap(k, parent(k)); k = parent(k);
}
}
删除堆顶元素

删除堆顶元素要注意维持堆的特殊性质。这里举个例子。

  • 要将这个堆中删除最大值,也就是堆顶元素62,先将62取出。

  • 将堆顶元素和堆的最后一个元素互换,也就是数组的首尾元素互换。

  • 删除最后一个元素,也就是堆中的最大值

  • 将当前的堆顶元素16的左右孩子41、30进行比较,找出最大的一个41,再与根节点16进行比较,左孩子41比根节点16大,所以将根节点与其左孩子互换,如上图所示。
  • 重复上面的操作,直到当前节点的值大于其左右子树。过程如下所示。

删除堆顶元素的代码实现
//看堆中最大的元素
public E findMax() {
if (data.size() == 0)
throw new IllegalArgumentException("Can not findMax when heap is empty");
return data.get(0);
} //取出堆中最大的元素
public E extractMax() {
E ret = findMax(); swap(0, data.size() - 1);
data.remove(data.size() - 1);
siftDown(0); return ret;
} private void siftDown(int k) {
//leftChild存在
while (leftChild(k) < data.size()) {
int j = leftChild(k);
//rightChild存在,且值大于leftChild的值
if (j + 1 < data.size() &&
data.get(j).compareTo(data.get(j + 1)) < 0)
j = rightChild(k);
//data[j]是leftChild和rightChild中最大的 if (data.get(k).compareTo(data.get(j)) >= 0)
break; swap(k, j);
k = j;
} }

Replace操作

Replace是指将堆中的最大元素取出,替换另一个进去。

自然地我们会想到使用之前的extractMax()和add()来实现,但是这样的时间复杂度将会是两次的O(log n),因此我们可以直接将堆顶元素替换以后执行sift down操作,这样时间复杂度就只有O(log n)。

Replace代码实现
//取出堆中最大的元素,替换为元素e
public E replace(E e){
E ret = findMax(); data.set(0, e);
siftDown(0); return ret;
}

Heapify操作

Heapify是指将数组转化为堆

这里我们先将数组直接看成是一个完全二叉树,然后找到这棵二叉树的最后一个非叶子节点的节点,也就是该树的最后一个节点的父节点。然后从这个节点开始到根节点结束,执行sift down操作。

这样的时间复杂度为O(n)

Heapify代码实现
//heapify操作:将数组转化为堆
public MaxHeap(E[] arrs) {
data = new ArrayList<>(Arrays.asList(arrs));
for (int i = parent(data.size() - 1); i >= 0; i--) {
siftDown(i);
}
}

java实现最大堆的更多相关文章

  1. java 创建最大堆

    最大堆的性质是除了根节点之外的所有节点(i)都需要满足A[PARENT(i)]>A[i],即其对应节点值小于其父节点对应值. 下面实现以数组int []a构建最大堆. public class ...

  2. java基础之:堆排序

    最近做题目饱受打击,愈发觉得打好基础的重要性,于是乎,决心把基本的排序算法还有数组操作一一实现,目的在于一方面能够得到对JAVA基础的巩固,另一面在实现的过程中发现不足. 今天所实现的堆排序(最大堆) ...

  3. 二叉堆(三)之 Java的实现

    概要 前面分别通过C和C++实现了二叉堆,本章给出二叉堆的Java版本.还是那句话,它们的原理一样,择其一了解即可. 目录1. 二叉堆的介绍2. 二叉堆的图文解析3. 二叉堆的Java实现(完整源码) ...

  4. OC、C#与JAVA语法特点一些异同(差集&交集)

    C#对JAVA: 1.扩展方法 2.部分类 3.动态对象 4.匿名返回类型 5.表达式树 6.Linq 7.没有函数指针,委托,事件的直接提供方式 8.JAVA接口不规定以I开头,这个很烂的思想! J ...

  5. Java进阶 JVM 内存与垃圾回收篇(一)

    JVM 1. 引言 1.1 什么是JVM? 定义 Java Vritual Machine - java 程序的运行环境(Java二进制字节码的运行环境) 好处 一次编译 ,到处运行 自动内存管理,垃 ...

  6. .NET技术在中国为什么老被人嫌弃

    这个话题有点自黑的意思,我从.NET 1.1开始玩.NET,到现在已经11年了,我是看着.NET成长起来,在中国壮大的,也见证了近几年.NET被各种嫌弃,其实说到底还是中国的架构师太少,我是说真正懂行 ...

  7. 转载:ZooKeeper Programmer's Guide(中文翻译)

    本文是为想要创建使用ZooKeeper协调服务优势的分布式应用的开发者准备的.本文包含理论信息和实践信息. 本指南的前四节对各种ZooKeeper概念进行较高层次的讨论.这些概念对于理解ZooKeep ...

  8. ZooKeeper程序员指南(转)

    译自http://zookeeper.apache.org/doc/trunk/zookeeperProgrammers.html 1 简介 本文是为想要创建使用ZooKeeper协调服务优势的分布式 ...

  9. zookeeper[1] (转)ZooKeeper Programmer's Guide(zookeeper编程向导)---中文

    原文:http://www.cnblogs.com/Xrinehart/p/3512509.html 本文是为想要创建使用ZooKeeper协调服务优势的分布式应用的开发者准备的.本文包含理论信息和实 ...

随机推荐

  1. mac home brew install go

    mac利器home brew安装Go 首先你得需要安装home brew和ruby环境(因为home brew依赖ruby) 如果没有请自行到链接安装 准备好之后就开始安装go了 brew updat ...

  2. mayan 游戏真是毒瘤

    如题 真坑呀!!! 可算过了 我率先达到了氧气富有化 先是改变时没有fall.40分 然后是fall函数写慢了 tle 50分 . 上代码 #include<cstdio> #includ ...

  3. IIS 处理程序“PageHandlerFactory-Integrated”

    出现这种错误是因为先安装了.net framework 4.0然后才安装了iis,此种情况下iis默认只支持.net framewrok 2.0,要解决此问题,需要在iis中注册.net framew ...

  4. jeDate日期控件

    http://www.jayui.com/jedate/     这是日期控件官网,可以去里面下载使用 前台 <%@ Page Language="C#" AutoEvent ...

  5. base_lr, blobs_lr

    caffe里面,原来以为是不可以随便调整学习率的,现在看来是可以的.base_lr是适用于所有层的学习率,而针对单个层,可以通过增加两个blobs_lr,用来调整该层的学习率,为什么是两个呢,因为一个 ...

  6. JS常见内置对象和方法

    JS中常用的内置对象:Array对象.Date对象.正则表达式对象.string对象.Global对象  Array对象中常用方法: concat() 表示把几个数组合并成一个数组join()   设 ...

  7. filebeat的安装及配置

    概述:Filebeat是一个日志文件托运工具,在你的服务器上安装客户端后,filebeat会监控日志目录或者指定的日志文件,追踪读取这些文件(追踪文件的变化,不停的读),并且转发这些信息到elasti ...

  8. 使用file_get_contents()和curl()抓取网络资源的效率对比

    使用file_get_contents()和curl()抓取网络资源的效率对比 在将小程序用户头像合成海报的时候,用到了抓取用户头像对应的网络资源,那么抓取方式有很多,比如 file_get_cont ...

  9. Laravel系列 Web

    一.Homestead准备 上一篇文章已经对它的配置进行了说明,下面对Homestead.yaml进行说明 --- ip: "192.168.10.10" memory: 2048 ...

  10. Linux下 VI 编辑器操作

    VI编辑器的三种模式:命令模式.输入模式.末行模式. 1.命令模式:vi启动后默认进入的是命令模式,从这个模式使用命令可以切换到另外两种模式,同时无论在何种模式下,[Esc]键都可以回到命令模式.在命 ...