概述

在谈堆之前,我们先了解什么是优先队列。我们每天都在排队,银行,医院,购物都得排队。排在队首先处理事情,处理完才能从这个队伍离开,又有新的人来排在队尾。但仅仅这样就能满足我们生活需求吗,明显不能。医院里,患者排队准备看病,这时有个重症患者入队,医生如果按队列的方式一个一个往下处理,等排到这位重病患者时,可能他就因为伤情过重挂了,之后就会引发医患纠纷,这明显不是我们想要的结果。优先队列就成为我们解决此类事情的关键,重病患者入队(挂号),医生根据他的伤情紧急(优先级)优先处理他的病情。

如果非要用专业术语来区分他们二者的区别

  1. 队列先进先出,后进后出
  2. 优先队列,出队与入队时的顺序无关,与优先级有关。

了解了优先队列,那这个堆又是什么玩意,可能很多人听过内存堆栈。特别要声明和注意的是,这里的堆仅仅是存储数据的一种结构方式,与内存的堆栈不是一个概念。

  1. 二叉堆是一颗完全二叉树结构(不懂什么是树的同学请面壁),说的通俗点,堆就是满足一些特殊性质的树,所以二叉堆就是有特殊性质的二叉树。
  2. 父节点的值大于(小于)两个子节点的值,又称为最大堆和最小堆,我们要定义的是最大堆(最小堆跟他相反)。

实例

我们先来看下什么是满的二叉树

每一层所有节点都有两个儿子结点的二叉树,就叫满的二叉树,计算他节点个数的公式2^3 - 1 = 7。有七个节点

完全二叉树(最大堆)

堆和优先队列有什么关系

知道了什么是堆和优先队列,它们之间有什么关系哪。说穿了就一句话,堆是优先队列这种数据结构的一种实现方式。

注意:优先队列可以用不同的底层实现(普通线性结构),时间复杂度不同。

数组实现完全二叉树(最大堆)

也可以定义二叉树来实现完全二叉树,但是通过观察会发现其结构的特点,都是用顺序存储方式存储。从1到n编号,就得到结点的一个线性系列。每一层结点个数恰好是上一层结点个数的2倍,也因此通过一个节点的编号就可以推知他的左右孩子节点的编号。

通过分析和数学归纳得出一个结论,很方便的知道他的左右孩子节点和父节点。

  1. 父节点 parent(i) = (i - 1) / 2,算下结点10的父节点 (7 - 1) / 2 = 3 就是 60
  2. 左孩子 left child(i) = 2 * i + 1,可以算出 10 的左孩子 7 * 2 + 1 = 15 > 7 (这里的7为最大索引值)没有左孩子这个结点
  3. 右孩子 right child(i) = 2 * i + 2,可以算出 10 的右孩子 7 * 2 + 2 = 16 > 7 没有右孩子这个结点

定义一个我们自己的数组Array类,也可以用Java提供的Array

Array类

  1. public class Array<E> {
  2.  
  3. private E[] data;
  4.  
  5. private int size;
  6.  
  7. //构造函数,传入数组的容量capacity构造Array
  8. public Array(int capacity) {
  9. this.data = (E[]) new Object[capacity];
  10. size = 0;
  11. }
  12. //无参数构造函数
  13. public Array() {
  14. this(10);
  15. }
  16.  
  17. //获取数组的个数
  18. public int getSize() {
  19. return size;
  20. }
  21.  
  22. //获取数组的容量
  23. public int getCapacity() {
  24. return data.length;
  25. }
  26.  
  27. //数组是否为空
  28. public boolean isEmpty() {
  29. return size == 0;
  30. }
  31.  
  32. //添加最后一个元素
  33. public void addLast(E e) {
  34. add(size,e);
  35. }
  36.  
  37. //添加第一个元素
  38. public void addFirst(E e){
  39. add(0,e);
  40. }
  41.  
  42. //获取inde索引位置的元素
  43. public E get(int index){
  44. if (index < 0 || index >= size){
  45. throw new IllegalArgumentException("Get failed,index is illegal");
  46. }
  47. return data[index];
  48. }
  49.  
  50. public void set(int index,E e){
  51. if (index < 0 || index >= size){
  52. throw new IllegalArgumentException("Get failed,index is illegal");
  53. }
  54. data[index] = e;
  55. }
  56.  
  57. //获取最后一个元素
  58. public E getLast(){
  59. return this.get(size - 1);
  60. }
  61.  
  62. //获取第一个元素
  63. public E getFirst(){
  64. return this.get(0);
  65. }
  66.  
  67. //添加元素
  68. public void add(int index,E e){
  69. if (index > size || index < 0){
  70. throw new IllegalArgumentException("add failed beceause index > size or index < 0,Array is full.");
  71. }
  72. if (size == data.length){
  73. resize(data.length * 2);
  74. }
  75. for (int i = size - 1; i >= index; i--) {
  76. data[i+1] = data[i];
  77. }
  78. data[index] = e;
  79. size ++;
  80.  
  81. }
  82.  
  83. //扩容数组
  84. private void resize(int newCapacity) {
  85. E[] newData = (E[]) new Object[newCapacity];
  86. for (int i = 0; i < size; i++) {
  87. newData[i] = data[i];
  88. }
  89. data = newData;
  90. }
  91.  
  92. public E[] getData() {
  93. return data;
  94. }
  95.  
  96. //查找数组中是否有元素e
  97. public boolean contains(E e){
  98. for (int i = 0; i < size; i++) {
  99. if (data[i].equals(e)){
  100. return true;
  101. }
  102. }
  103. return false;
  104. }
  105.  
  106. //根据元素查看索引
  107. public int find(E e){
  108. for (int i = 0; i < size; i++) {
  109. if (data[i].equals(e)){
  110. return i;
  111. }
  112. }
  113. return -1;
  114. }
  115.  
  116. //删除某个索引元素
  117. public E remove(int index){
  118. if(index < 0 || index >= size){
  119. throw new IllegalArgumentException("detele is fail,index < 0 or index >= size");
  120. }
  121. E ret = data[index];
  122. for (int i = index + 1; i < size; i++) {
  123. data[i - 1] = data[i];
  124. }
  125. size --;
  126. data[size] = null;
  127. if (size < data.length / 2){
  128. resize(data.length / 2);
  129. }
  130. return ret;
  131. }
  132.  
  133. //删除首个元素
  134. public E removeFirst(){
  135. return this.remove(0);
  136. }
  137.  
  138. //删除最后一个元素
  139. public E removeLast(){
  140. return this.remove(size - 1);
  141. }
  142.  
  143. //从数组删除元素e
  144. public void removeElemen(E e){
  145. int index = find(e);
  146. if (index != -1){
  147. remove(index);
  148. }
  149.  
  150. }
  151.  
  152. @Override
  153. public String toString() {
  154. StringBuilder sb = new StringBuilder("");
  155. sb.append(String.format("Array:size = %d,capacity = %d \n",size,data.length));
  156. sb.append("[");
  157. for (int i = 0; i < size; i++) {
  158. sb.append(data[i]);
  159. if (i != size - 1){
  160. sb.append(",");
  161. }
  162. }
  163. sb.append("]");
  164. return sb.toString();
  165. }
  166. }

有了Array数组类,接下来很快的,把我们刚才描述的事情用代码实现出来之后,在考虑出队和入队的操作,因为父节点要大于或小于他们的子节点。所以我们的节点要能相互比较,在Java继承Comparable类就可以了。

最大堆(MaxHeap类)

  1. public class MaxHeap<E extends Comparable<E>>
  2. {
  3. private Array<E> data;
  4.  
  5. public MaxHeap(int capacity)
  6. {
  7. data = new Array<>(capacity);
  8. }
  9.  
  10. public MaxHeap()
  11. {
  12. data = new Array<>();
  13. }
  14.  
  15. //堆里的元素个数
  16. public int size()
  17. {
  18. return data.getSize();
  19. }
  20.  
  21. //堆是否为空
  22. public boolean isEmpty()
  23. {
  24. return data.isEmpty();
  25. }
  26.  
  27. //根据一个元素的索引,获取他父亲索引
  28. private int parent(int index)
  29. {
  30. if (index == 0)
  31. {
  32. throw new IllegalArgumentException("index - 0 does't have parent.");
  33. }
  34. return (index - 1) / 2;
  35. }
  36.  
  37. //根据一个元素的索引,获取他右孩子的索引
  38. private int leftChild(int index)
  39. {
  40.  
  41. return index * 2 + 1;
  42. }
  43.  
  44. //根据一个元素的索引,获取他左孩子的索引
  45. private int rightChild(int index)
  46. {
  47. return index * 2 + 2;
  48. }
  49.  
  50. }

向堆中添加一个元素,在堆的内部要进行一个上浮的操作,保证用数组实现的二叉堆还符合我们最大堆的性质(父节点的值大于两个子节点的值)。

82大于他的父节点60,两个结点交换位置,82还大于他的父结点80,两个节点交换位置。80小于现在的父结点90,结束交换。这个操作很多人称为上浮操作(个人认为名称贴切)上浮操作完成。

用代码实现我们刚才的操作,已经知道他父结点的位置(公式),交换两个人的位置就变得很简单,MaxHeap添加函数。

  1. //堆中添加元素
  2. public void add(E e)
  3. {
  4. data.addLast(e);
  5. siftUp(data.getSize() - 1);
  6. }
  7.  
  8. //上浮操作
  9. private void siftUp(int i)
  10. {
  11. while (i > 0 && data.get(parent(i)).compareTo(data.get(i)) < 0)
  12. {
  13. //交换位置
  14. data.swap(i,parent(i));
           i = parenti
  15. }
  16. }

Array类,添加交换位置的函数

  1. public void swap(int i,int j)
  2. {
  3. if (i < 0 || i >= size || j < 0 || j > size)
  4. {
  5. throw new IllegalArgumentException("索引越界");
  6. }
  7. E t = data[i];
  8. data[i] = data[j];
  9. data[j] = t;
  10.  
  11. }

有添加就有取出,取出堆中元素其实很简单,因为最大堆决定了只取堆顶元素(数组的第一个元素),直接取出即可。困难的是如何维护二叉堆的性质不变。

取出堆顶元素后

取出堆顶元素,剩下两个子树,将两颗子树糅合成一个二叉堆,现在直接将60这个元素作为堆顶,就满足了完全二叉树的性质但并不符合最大堆性质。

和上浮的操作相反,现在我们要进行下沉的操作,60的左右孩子都比60来得大,要选择左右孩子最大的那个数进行交换,82和60进行交换,80比60来得大,交换他们的位置,10比60来得小,符合二叉堆的性质。交换结束。

用代码描述刚才取出的操作。

MaxHeap类

  1. //堆中最大元素
  2. public E findMax()
  3. {
  4. if (data.getSize() == 0)
  5. {
  6. throw new IllegalArgumentException("堆为空,无法查看值");
  7. }
  8. return data.get(0);
  9. }
  10. //取出堆顶元素
  11. public E extractMax()
  12. {
  13. E ret = findMax();
  14. data.swap(0,data.getSize() - 1);
  15. data.removeLast();
  16. siftDown(0);
  17. return ret;
  18. }
  19.  
  20. //下沉操作
  21. private void siftDown(int i)
  22. {
  23. //比较到他左右孩子那个比他大进行交换操作
  24. while (leftChild(i) < data.getSize())
  25. {
  26. int j = leftChild(i);
  27. if (j + 1 < data.getSize() && data.get(j + 1).compareTo(data.get(j)) > 0) //右节点
  28. {
  29. j = rightChild(i);
  30. }
  31. if (data.get(i).compareTo(data.get(j)) >= 0)
  32. {
  33. break;
  34. }
  35. data.swap(i,j);
  36. i = j;
  37. }
  38. }

现在我们堆结构基本完成,简单测试一下

Main类

  1. public class Main
  2. {
  3. public static void main(String[] args) {
  4. MaxHeap<Integer> maxHeap = new MaxHeap<>();
  5. int[] nums = {90,80,70,60,50,60,20,10};
  6. for (int i = 0; i < nums.length; i++)
  7. {
  8. maxHeap.add(nums[i]);
  9. }
  10. System.out.println("堆顶:" + maxHeap.findMax());
  11. maxHeap.add(82);//添加82
  12. System.out.println("取出堆顶值:" + maxHeap.extractMax());
  13. System.out.println("堆顶:" + maxHeap.findMax());//是否为82
  14. maxHeap.add(85);//添加85
  15. System.out.println("堆顶:" + maxHeap.findMax()); //是否为85
  16. System.out.println("测试结束");
  17. }
  18. }

输出

  1. 堆顶:90
  2. 取出堆顶值:90
  3. 堆顶:82
  4. 堆顶:85
  5. 测试结束

用定义的最大堆去实现一个优先队列就变得十分简单了,优先队列本质上来说还是一个队列,用堆来实现队列的接口。

Queue接口类

  1. public interface Queue<E> {
  2.  
  3. int getSize();
  4.  
  5. boolean isEmpty();
  6.  
  7. void enqueue(E e);
  8.  
  9. E dequeue();
  10.  
  11. E getFront();
  12. }

优先队列(PriorityQueue类)

  1. public class PriorityQueue<E extends Comparable<E>> implements Queue<E>
  2. {
  3. private MaxHeap<E> maxHeap;
  4.  
  5. public PriorityQueue()
  6. {
  7. maxHeap = new MaxHeap<>();
  8. }
  9.  
  10. @Override
  11. public int getSize() {
  12. return maxHeap.size();
  13. }
  14.  
  15. @Override
  16. public boolean isEmpty() {
  17. return maxHeap.isEmpty();
  18. }
  19.  
  20. @Override
  21. public void enqueue(E e) {
  22. maxHeap.add(e);
  23. }
  24.  
  25. @Override
  26. public E dequeue() {
  27. return maxHeap.extractMax();
  28. }
  29.  
  30. @Override
  31. public E getFront() {
  32. return maxHeap.findMax();
  33. }
  34. }

实例

在股票市场,很多股民向股票代理打电话,股票代理公司优先处理vip客户(有钱¥)再处理普通的用户。把他们的money当做他们的优先程度

Customer类

  1. public class Customer implements Comparable<Customer> {
  2. private int money;
  3.  
  4. private String name;
  5.  
  6. public Customer(int money, String name) {
  7. this.money = money;
  8. this.name = name;
  9. }
  10.  
  11. public int getMoney() {
  12. return money;
  13. }
  14.  
  15. public void setMoney(int money) {
  16. this.money = money;
  17. }
  18.  
  19. public String getName() {
  20. return name;
  21. }
  22.  
  23. public void setName(String name) {
  24. this.name = name;
  25. }
  26.  
  27. @Override
  28. public int compareTo(Customer another) {
  29. if (this.money < another.money)
  30. {
  31. return -1;
  32. }else if (this.money > another.money)
  33. {
  34. return 1;
  35. }else {
  36. return 0;
  37. }
  38. }
  39. }

Main类

  1. public class Main
  2. {
  3. public static void main(String[] args) {
  4. //优先队列使用示例
  5. Queue<Customer> queue = new PriorityQueue<>();
  6. Random random = new Random();
  7. for (int i = 0; i < 10; i++)
  8. {
  9. int money = random.nextInt(1000000);
  10. queue.enqueue(new Customer(money,"客户" + i ));
  11. }
  12. while (true)
  13. {
  14. if (queue.isEmpty())
  15. {
  16. break;
  17. }
  18. Customer customer = queue.dequeue();
  19. System.out.println("优先处理 " + customer.getName() + " 因为他的money为:" + customer.getMoney() + "¥");
  20. }
  21. }
  22.  
  23. }

输出

  1. 优先处理 客户4 因为他的money为:842917
  2. 优先处理 客户7 因为他的money为:628183
  3. 优先处理 客户8 因为他的money为:578457
  4. 优先处理 客户0 因为他的money为:551270
  5. 优先处理 客户1 因为他的money为:538859
  6. 优先处理 客户5 因为他的money为:297316
  7. 优先处理 客户3 因为他的money为:262908
  8. 优先处理 客户9 因为他的money为:250763
  9. 优先处理 客户6 因为他的money为:144102
  10. 优先处理 客户2 因为他的money为:96273

随机数,输出结果不确定。但一定是从大到小排序,如果要从小到大很简单,改比较符即可。这边实现的是最大堆,Java提供的优先队列(PriorityQueue)底层是最小堆。

============================================

如发现错误请留言提醒lz,好及时修改,避免误导别人。拜谢

Java数据结构之堆和优先队列的更多相关文章

  1. java数据结构之(堆)栈

    (堆)栈概述栈是一种特殊的线性表,是操作受限的线性表栈的定义和特点•定义:限定仅在表尾进行插入或删除操作的线性表,表尾—栈顶,表头—栈底,不含元素的空表称空栈•特点:先进后出(FILO)或后进先出(L ...

  2. 数据结构-堆实现优先队列(java)

    队列的特点是先进先出.通常都把队列比喻成排队买东西,大家都非常守秩序,先排队的人就先买东西. 可是优先队列有所不同,它不遵循先进先出的规则,而是依据队列中元素的优先权,优先权最大的先被取出. 这就非常 ...

  3. Java数据结构和算法(十四)——堆

    在Java数据结构和算法(五)——队列中我们介绍了优先级队列,优先级队列是一种抽象数据类型(ADT),它提供了删除最大(或最小)关键字值的数据项的方法,插入数据项的方法,优先级队列可以用有序数组来实现 ...

  4. Java数据结构和算法 - 堆

    堆的介绍 Q: 什么是堆? A: 这里的“堆”是指一种特殊的二叉树,不要和Java.C/C++等编程语言里的“堆”混淆,后者指的是程序员用new能得到的计算机内存的可用部分 A: 堆是有如下特点的二叉 ...

  5. java数据结构和算法10(堆)

    这篇我们说说堆这种数据结构,其实到这里就暂时把java的数据结构告一段落,感觉说的也差不多了,各种常见的数据结构都说到了,其实还有一种数据结构是“图”,然而暂时对图没啥兴趣,等有兴趣的再说:还有排序算 ...

  6. java数据结构----堆

    1.堆:堆是一种树,由它实现的优先级队列的插入和删除的时间复杂度都是O(logn),用堆实现的优先级队列虽然和数组实现相比较删除慢了些,但插入的时间快的多了.当速度很重要且有很多插入操作时,可以选择堆 ...

  7. Java数据结构和算法 - 栈和队列

    Q: 栈.队列与数组的区别? A: 本篇主要涉及三种数据存储类型:栈.队列和优先级队列,它与数组主要有如下三个区别: A: (一)程序员工具 数组和其他的结构(栈.队列.链表.树等等)都适用于数据库应 ...

  8. Java数据结构和算法(四)赫夫曼树

    Java数据结构和算法(四)赫夫曼树 数据结构与算法目录(https://www.cnblogs.com/binarylei/p/10115867.html) 赫夫曼树又称为最优二叉树,赫夫曼树的一个 ...

  9. java——数据结构

    底层数据结构: 数组 ArrayList 链表 LinkedList 应用数据结构: 二分搜索树 BST 最大堆/最小堆 MaxHeap/MinHeap 线段树 SegmentTree 字典树 Tri ...

随机推荐

  1. H5之postMessage 。实现跨域

    对于跨域我们有很多的解决方案,今天我来分享一下postMessage的那点事,postMessage是html5新增的一个解决跨域的一个方法,不过很可惜万恶的ie6,7不支持 postMessage( ...

  2. ImageMagick

    http://blog.csdn.net/lan861698789/article/details/7738383 1.官网 http://www.imagemagick.org/script/ind ...

  3. 使用Node.js完成的第一个项目的实践总结

    http://blog.csdn.net/yanghua_kobe/article/details/17199417 项目简介 这是一个资产管理项目,主要的目的就是实现对资产的无纸化管理.通过为每个资 ...

  4. 数据库中row_number()、rank()、dense_rank() 的区别

    row_number的用途非常广泛,排序最好用它,它会为查询出来的每一行记录生成一个序号,依次排序且不会重复,注意使用row_number函数时必须要用over子句选择对某一列进行排序才能生成序号. ...

  5. log4j配置及使用

    一.使用方法: 1.将log4j.properties放到你创建项目的src中 2.引入log4j.jar import org.apache.log4j.*; public class log4jT ...

  6. 第一课:Hadoop集群环境搭建

    一. 检查列表 1.1.网络访问 设置电脑IP以及可以访问网络设置:进入etc/sysconfig/network-scripts/,使用命令"ls -all" 查看文件.会看到i ...

  7. 使用pypi-server搭建简单的PyPI源

    pypiserver 是一个最基本的PyPI服务器实现, 可以用来上传和维护python包. 本文介绍 pypiserver 在ubuntu上的基本安装, 配置和使用. 1. 基本安装和使用 1.1 ...

  8. mysql 和 oracle 的一些小知识

    有很多应用项目, 刚起步的时候用MYSQL数据库基本上能实现各种功能需求,随着应用用户的增多,数据量的增加,MYSQL渐渐地出现不堪重负的情况:连接很慢甚至宕机,于是就有把数据从MYSQL迁到ORAC ...

  9. Java开源生鲜电商平台-账单模块的设计与架构(源码可下载)

    Java开源生鲜电商平台-账单模块的设计与架构(源码可下载) 补充说明:Java开源生鲜电商平台-账单模块的设计与架构,即用户的账单形成过程. 由于系统存在一个押账功能的需求,(何为押账,就是形成公司 ...

  10. sql server按符号截取字符串

    http://www.360doc.com/content/12/0626/13/1912775_220523992.shtml