您的位置:首页 > 其它

排序算法之——优先队列经典实现(基于二叉堆)

2018-09-17 20:31 1016 查看
许多应用都需要处理有序的元素,但有时,我们不要求所有元素都有序,或是一定要一次就将它们排序,许多情况下,我们会收集这些元素里的最大值或最小值。

这种情况下一个合适的数据结构应该支持两种操作:插入元素、删除最大元素。

优先队列与栈和队列类似,但它有自己的奇妙之处。

在本文中,会讲解基于二叉堆的一种优先队列的经典实现方法(代码没有任何难度,主要是理解思想)。

一、关于堆

1、堆的定义:

数据结构二叉堆能很好地实现优先队列的操作。在二叉堆中,每个元素都要保证大于等于另外两个位置的元素,相应的,这些位置的元素又至少要大于等于数组中的另外两个元素。

将所有元素画成一颗二叉树,就能很容易看出这种结构。



(图示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;
}
}


二、堆排序(非降序):



(示意图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());
}*/
}

}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: