排序算法之——优先队列经典实现(基于二叉堆)
2018-09-17 20:31
1016 查看
许多应用都需要处理有序的元素,但有时,我们不要求所有元素都有序,或是一定要一次就将它们排序,许多情况下,我们会收集这些元素里的最大值或最小值。
这种情况下一个合适的数据结构应该支持两种操作:插入元素、删除最大元素。
优先队列与栈和队列类似,但它有自己的奇妙之处。
在本文中,会讲解基于二叉堆的一种优先队列的经典实现方法(代码没有任何难度,主要是理解思想)。
将所有元素画成一颗二叉树,就能很容易看出这种结构。
![](https://oscdn.geek-share.com/Uploads/Images/Content/201809/0e71edf29761423d81643e9b96e0f608.png)
(图示1)
某个节点的优先级上升,我们需要由下至上恢复堆的顺序。
当某个节点的优先级下降,我们需要由上至下恢复堆的顺序。
在排序算法中,我们只通过私有辅助函数来访问元素:
k/2即为k节点的父节点,当k大于k/2时交换两者,并继续与其父节点比较,直到找到合适的位置。
对于二叉树,2*k即为k的左子节点,将左右子节点进行比较,再将父节点与较大的子节点比较,如果子节点大于父节点,就将他们交换,并继续向下比较,直到找到合适的位置。
在插入方法中,如果空间已满,就将数组大小扩展为原来的两倍。在删除方法中,如果元素的个数小于数组长度的1/4,就将数组的长度减小一半。
有了上面的方法,我们只需在插入和删除方法中加入判断语句即可。
![](https://oscdn.geek-share.com/Uploads/Images/Content/201809/993735196373b15fe2b5fc28d3e808e3.png)
(示意图2)
堆排序的sink()方法经过修改sink(a,b)中a是被排序的元素,b为排序的最大范围(修改之前排序的最大范围为元素总个数)。
1、heap construction(堆的构造)
可以看到在for循环中,我们只扫描了数组一半元素,因为我们跳过了大小为1的子堆,每次对一个节点排序时,以该节点为根节点的子堆就是有序的,所以我们最后会得到一个堆有序的二叉堆。
这种情况下一个合适的数据结构应该支持两种操作:插入元素、删除最大元素。
优先队列与栈和队列类似,但它有自己的奇妙之处。
在本文中,会讲解基于二叉堆的一种优先队列的经典实现方法(代码没有任何难度,主要是理解思想)。
一、关于堆
1、堆的定义:
数据结构二叉堆能很好地实现优先队列的操作。在二叉堆中,每个元素都要保证大于等于另外两个位置的元素,相应的,这些位置的元素又至少要大于等于数组中的另外两个元素。将所有元素画成一颗二叉树,就能很容易看出这种结构。
![](https://oscdn.geek-share.com/Uploads/Images/Content/201809/0e71edf29761423d81643e9b96e0f608.png)
(图示1)
2、堆的算法:
在堆有序的过程中我们会遇到两种情况:某个节点的优先级上升,我们需要由下至上恢复堆的顺序。
当某个节点的优先级下降,我们需要由上至下恢复堆的顺序。
在排序算法中,我们只通过私有辅助函数来访问元素:
private void exch(int i, int j) { Key temp = pq[i]; pq[i] = pq[j]; pq[j] = temp; } private boolean less(int i, int j) { return pq[i].compareTo(pq[j]) < 0; }
①、由下至上的堆的有序化(上浮)
private void swim(int k) {// 二叉堆 while (k > 1 && less(k / 2, k)) { exch(k / 2, k); k = k / 2; } }
k/2即为k节点的父节点,当k大于k/2时交换两者,并继续与其父节点比较,直到找到合适的位置。
②、由上至下的堆的有序化(下沉)
private void sink(int k) {// 二叉堆 while (2 * k <= N) { int j = 2 * k; if (j < N && less(j, j + 1)) { j++; } if (!less(k, j)) { break; } exch(k, j); k = j; } }
对于二叉树,2*k即为k的左子节点,将左右子节点进行比较,再将父节点与较大的子节点比较,如果子节点大于父节点,就将他们交换,并继续向下比较,直到找到合适的位置。
③、调整数组大小
如果不知道元素的个数,任意在初始化时造成空间的浪费。我们需要创造一个函数,用来调整数组的大小。在插入方法中,如果空间已满,就将数组大小扩展为原来的两倍。在删除方法中,如果元素的个数小于数组长度的1/4,就将数组的长度减小一半。
private void resize(int n) { Key[] temp = (Key[]) new Comparable[n + 1]; for (int i = 1; i <= N; i++) { temp[i] = pq[i]; } pq = temp; L = n; }
有了上面的方法,我们只需在插入和删除方法中加入判断语句即可。
④、多叉堆(了解即可)
在掌握了二叉堆的原理之后,将其改进为多叉堆只需要做几个改动。下面直接放代码,有兴趣的朋友可以自己动手。private void swim(int k, int d) {// d叉堆:(k+d-2)/d为d叉堆第k个节点的父节点 while (k > 1 && less((k + d - 2) / d, k)) { exch((k + d - 2) / d, k); k = (k + d - 2) / d; } } private void sinkM(int k, int d) {// d叉堆 while (d * k - (d - 2) <= N) {// d叉堆k节点的第一个子节点 int j = d * k - (d - 2); int flag = k; while (j <= N && j <= d * k + 1) { if (less(flag, j)) { flag = j; } j++; } if (flag == k) {// flag没有改变 break; } exch(k, flag); k = flag; } }
二、堆排序(非降序):
![](https://oscdn.geek-share.com/Uploads/Images/Content/201809/993735196373b15fe2b5fc28d3e808e3.png)
(示意图2)
堆排序的sink()方法经过修改sink(a,b)中a是被排序的元素,b为排序的最大范围(修改之前排序的最大范围为元素总个数)。
public void sort(Comparable[] a) {//堆排序 int n=N; for(int k=n/2;k>=1;k--) { sink(k,n); } while(n>1) { exch(1,n--); sink(1,n); } }
1、heap construction(堆的构造)
可以看到在for循环中,我们只扫描了数组一半元素,因为我们跳过了大小为1的子堆,每次对一个节点排序时,以该节点为根节点的子堆就是有序的,所以我们最后会得到一个堆有序的二叉堆。2、sortdown(下沉排序)
下沉排序每次选出最大的元素放入数组空出的位置,这有点像选择排序,但所需的比较要小得多,因为堆提供了一种从未排序部分找到最大元素的有效方法。三、java代码展示(所有代码)
public class MaxPQ<Key extends Comparable<Key>> { private Key[] pq; private static int N = 0;// 元素个数 private static int L;// 数组长度(不包括0) public MaxPQ(int maxN) { pq = (Key[]) new Comparable[maxN + 1]; L = maxN; } public boolean isEmpty() { return N == 0; } public int size() { return N; } public void insert(Key v) {// 二叉堆 if (N == L) { resize(2 * N + 1); System.out.println("resize Double"); } pq[++N] = v; swim(N); } public void insert(Key v, int d) {// d叉堆 if (N == L) { resize(2 * N + 1); System.out.println("resize Double"); } pq[++N] = v; swim(N, d); } public Key delMax() { Key max = pq[1]; exch(1, N--); pq[N + 1] = null; sink(1); if (N > 0 && N == L / 4) { resize(L / 2); System.out.println("resize 1/2"); } return max; } public Key delMax(int d) { if (N == 0) { System.out.println("none!"); } Key max = pq[1]; exch(1, N--); pq[N + 1] = null; sinkM(1, d); if (N > 0 && N == L / 4) { resize(L / 2); System.out.println("resize 1/2"); } return max; } private void swim(int k) {// 二叉堆 while (k > 1 && less(k / 2, k)) { exch(k / 2, k); k = k / 2; } } private void swim(int k, int d) {// d叉堆:(k+d-2)/d为d叉堆第k个节点的父节点 while (k > 1 && less((k + d - 2) / d, k)) { exch((k + d - 2) / d, k); k = (k + d - 2) / d; } } private void sink(int k) {// 二叉堆 while (2 * k <= N) { int j = 2 * k; if (j < N && less(j, j + 1)) { j++; } if (!less(k, j)) { break; } exch(k, j); k = j; } } private void sinkM(int k, int d) {// d叉堆 while (d * k - (d - 2) <= N) {// d叉堆k节点的第一个子节点 int j = d * k - (d - 2); int flag = k; while (j <= N && j <= d * k + 1) { if (less(flag, j)) { flag = j; } j++; } if (flag == k) {// flag没有改变 break; } exch(k, flag); k = flag; } } private void resize(int n) { Key[] temp = (Key[]) new Comparable[n + 1]; for (int i = 1; i <= N; i++) { temp[i] = pq[i]; } pq = temp; L = n; } private void exch(int i, int j) { Key temp = pq[i]; pq[i] = pq[j]; pq[j] = temp; } private boolean less(int i, int j) { return pq[i].compareTo(pq[j]) < 0; } public void sort(Comparable[] a) {//堆排序 int n=N; for(int k=n/2;k>=1;k--) { sink(k,n); } while(n>1) { exch(1,n--); sink(1,n); } } private void sink(int k, int n) {//二叉树 从k到n排序 while (2 * k <= n) { int j = 2 * k; if (j < n && less(j, j + 1)) { j++; } if (!less(k, j)) { break; } exch(k, j); k = j; } } public static void main(String[] args) { MaxPQ mpq = new MaxPQ<>(3); mpq.insert(1); mpq.insert(2); mpq.insert(3); mpq.insert(4); mpq.insert(5); mpq.insert(6); mpq.insert(7); mpq.insert(8); mpq.insert(9); mpq.insert(10); mpq.insert(11); mpq.sort(mpq.pq); for(int i=1;i<=N;i++) { System.out.println(mpq.pq[i]); } /*for (int i = 1; i <= 13; i++) { System.out.println(mpq.delMax()); }*/ } }
相关文章推荐
- C#实现优先队列 基于二叉堆 附使用案例
- 基于二叉堆实现的优先队列和堆排序
- 使用二叉堆实现优先队列
- Python 使用由单链表构建的数组实现有边际优先队列 (基于class, 包含迭代器)
- 数据结构之优先队列--二叉堆(Java实现)
- 数据结构_使用二叉堆实现优先队列
- 基于堆的优先队列实现
- (数据结构与算法分析 七)------优先队列中的二叉堆的实现( Java语言描述)
- 二叉堆实现优先队列中的上滤和下滤
- [模板] 二叉堆 - 优先队列的二叉堆数组实现
- 基于 Python 的数据结构与算法分析学习记录(6-8)—— 基于二叉堆的优先队列
- 算法学习 - 优先队列的二叉堆实现
- 优先队列的Java实现(最大二叉堆)
- 经典的生产者与消费者模型(基于BlockingQueue队列实现)
- 算法设计之,堆,堆排序,基于最大堆的最大优先队列的实现(C++实现)
- 自己写GoBinaryHead 二叉堆binaryheap实现优先队列(堆)
- 优先队列(二叉堆)的基本实现
- 用二叉堆实现优先队列
- Python 使用list实现无边际优先队列 (基于class, 包含迭代器)
- Java基于堆结构实现优先队列功能示例