您的位置:首页 > 理论基础 > 数据结构算法

Java数据结构与算法解析(十四)——二叉堆

2017-11-02 09:14 281 查看


二叉堆概述

二叉堆是完全二元树或者是近似完全二元树,按照数据的排列方式可以分为两种:最大堆和最小堆。 

最大堆:父结点的键值总是大于或等于任何一个子节点的键值;最小堆:父结点的键值总是小于或等于任何一个子节点的键值。

二叉堆一般都通过”数组”来实现,下面是数组实现的最大堆和最小堆的示意图: 




二叉堆的实现

本实现以”最大堆”为例子来进行介绍。


1. 添加

假设在最大堆[90,80,70,60,40,30,20,10,50]种添加85,需要执行的步骤如下: 



当向最大堆中添加数据时:先将数据加入到最大堆的最后,然后尽可能把这个元素往上挪,直到挪不动! 

将85添加到[90,80,70,60,40,30,20,10,50]中后,最大堆变成了[90,85,70,60,80,30,20,10,50,40]。

最大堆的插入代码
/*
* 最大堆的向上调整算法(从start开始向上直到0,调整堆)
*
* 注:数组实现的堆中,第N个节点的左孩子的索引值是(2N+1),右孩子的索引是(2N+2)。
*
* 参数说明:
*     start -- 被上调节点的起始位置(一般为数组中最后一个元素的索引)
*/
protected void filterup(int start) {
int c = start;            // 当前节点(current)的位置
int p = (c-1)/2;        // 父(parent)结点的位置
T tmp = mHeap.get(c);        // 当前节点(current)的大小

while(c > 0) {
int cmp = mHeap.get(p).compareTo(tmp);
if(cmp >= 0)
break;
else {
mHeap.set(c, mHeap.get(p));
c = p;
p = (p-1)/2;
}
}
mHeap.set(c, tmp);
}

/*
* 将data插入到二叉堆中
*/
public void insert(T data) {
int size = mHeap.size();

mHeap.add(data);    // 将"数组"插在表尾
filterup(size);        // 向上调整堆
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35

insert(data)的作用:将数据data添加到最大堆中。mHeap是动态数组ArrayList对象。 

当堆已满的时候,添加失败;否则data添加到最大堆的末尾。然后通过上调算法重新调整数组,使之重新成为最大堆。


2. 删除

假设从最大堆[90,85,70,60,80,30,20,10,50,40]中删除90,需要执行的步骤如下: 



当从最大堆中删除数据时:先删除该数据,然后用最大堆中最后一个的元素插入这个空位;接着,把这个“空位”尽量往上挪,直到剩余的数据变成一个最大堆。 

从[90,85,70,60,80,30,20,10,50,40]删除90之后,最大堆变成了[85,80,70,60,40,30,20,10,50]。

注意:考虑从最大堆[90,85,70,60,80,30,20,10,50,40]中删除60,执行的步骤不能单纯的用它的子节点来替换;而必须考虑到”替换后的树仍然要是最大堆”! 



二叉堆的删除代码
/*
* 最大堆的向下调整算法
*
* 注:数组实现的堆中,第N个节点的左孩子的索引值是(2N+1),右孩子的索引是(2N+2)。
*
* 参数说明:
*     start -- 被下调节点的起始位置(一般为0,表示从第1个开始)
*     end   -- 截至范围(一般为数组中最后一个元素的索引)
*/
protected void filterdown(int start, int end) {
int c = start;          // 当前(current)节点的位置
int l = 2*c + 1;     // 左(left)孩子的位置
T tmp = mHeap.get(c);    // 当前(current)节点的大小

while(l <= end) {
int cmp = mHeap.get(l).compareTo(mHeap.get(l+1));
// "l"是左孩子,"l+1"是右孩子
if(l < end && cmp<0)
l++;        // 左右两孩子中选择较大者,即mHeap[l+1]
cmp = tmp.compareTo(mHeap.get(l));
if(cmp >= 0)
break;        //调整结束
else {
mHeap.set(c, mHeap.get(l));
c = l;
l = 2*l + 1;
}
}
mHeap.set(c, tmp);
}

/*
* 删除最大堆中的data
*
* 返回值:
*      0,成功
*     -1,失败
*/
public int remove(T data) {
// 如果"堆"已空,则返回-1
if(mHeap.isEmpty() == true)
return -1;

// 获取data在数组中的索引
int index = mHeap.indexOf(data);
if (index==-1)
return -1;

int size = mHeap.size();
mHeap.set(index, mHeap.get(size-1));// 用最后元素填补
mHeap.remove(size - 1);                // 删除最后的元素

if (mHeap.size() > 1)
filterdown(index, mHeap.size()-1);    // 从index号位置开始自上向下调整为最小堆

return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57


完整代码

二叉堆(最大堆)的实现
public class MaxHeap<T extends Comparable<T>> {

private List<T> mHeap; // 队列(实际上是动态数组ArrayList的实例)

public MaxHeap() {
this.mHeap = new ArrayList<T>();
}

/* * 最大堆的向下调整算法 * * 注:数组实现的堆中,第N个节点的左孩子的索引值是(2N+1),右孩子的索引是(2N+2)。 * * 参数说明: * start -- 被下调节点的起始位置(一般为0,表示从第1个开始) * end -- 截至范围(一般为数组中最后一个元素的索引) */ protected void filterdown(int start, int end) { int c = start; // 当前(current)节点的位置 int l = 2*c + 1; // 左(left)孩子的位置 T tmp = mHeap.get(c); // 当前(current)节点的大小 while(l <= end) { int cmp = mHeap.get(l).compareTo(mHeap.get(l+1)); // "l"是左孩子,"l+1"是右孩子 if(l < end && cmp<0) l++; // 左右两孩子中选择较大者,即mHeap[l+1] cmp = tmp.compareTo(mHeap.get(l)); if(cmp >= 0) break; //调整结束 else { mHeap.set(c, mHeap.get(l)); c = l; l = 2*l + 1; } } mHeap.set(c, tmp); } /* * 删除最大堆中的data * * 返回值: * 0,成功 * -1,失败 */ public int remove(T data) { // 如果"堆"已空,则返回-1 if(mHeap.isEmpty() == true) return -1; // 获取data在数组中的索引 int index = mHeap.indexOf(data); if (index==-1) return -1; int size = mHeap.size(); mHeap.set(index, mHeap.get(size-1));// 用最后元素填补 mHeap.remove(size - 1); // 删除最后的元素 if (mHeap.size() > 1) filterdown(index, mHeap.size()-1); // 从index号位置开始自上向下调整为最小堆 return 0; }

/* * 最大堆的向上调整算法(从start开始向上直到0,调整堆) * * 注:数组实现的堆中,第N个节点的左孩子的索引值是(2N+1),右孩子的索引是(2N+2)。 * * 参数说明: * start -- 被上调节点的起始位置(一般为数组中最后一个元素的索引) */ protected void filterup(int start) { int c = start; // 当前节点(current)的位置 int p = (c-1)/2; // 父(parent)结点的位置 T tmp = mHeap.get(c); // 当前节点(current)的大小 while(c > 0) { int cmp = mHeap.get(p).compareTo(tmp); if(cmp >= 0) break; else { mHeap.set(c, mHeap.get(p)); c = p; p = (p-1)/2; } } mHeap.set(c, tmp); } /* * 将data插入到二叉堆中 */ public void insert(T data) { int size = mHeap.size(); mHeap.add(data); // 将"数组"插在表尾 filterup(size); // 向上调整堆 }

@Override
public String toString() {
StringBuilder sb = new StringBuilder();
for (int i=0; i<mHeap.size(); i++)
sb.append(mHeap.get(i) +" ");

return sb.toString();
}

public static void main(String[] args) {
int i;
int a[] = {10, 40, 30, 60, 90, 70, 20, 50, 80};
MaxHeap<Integer> tree=new MaxHeap<Integer>();

System.out.printf("== 依次添加: ");
for(i=0; i<a.length; i++) {
System.out.printf("%d ", a[i]);
tree.insert(a[i]);
}

System.out.printf("\n== 最 大 堆: %s", tree);

i=85;
tree.insert(i);
System.out.printf("\n== 添加元素: %d", i);
System.out.printf("\n== 最 大 堆: %s", tree);

i=90;
tree.remove(i);
System.out.printf("\n== 删除元素: %d", i);
System.out.printf("\n== 最 大 堆: %s", tree);
System.out.printf("\n");
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136

二叉堆(最小堆)的实现文件
public class MinHeap<T extends Comparable<T>> {

private List<T> mHeap;        // 存放堆的数组

public MinHeap() {
this.mHeap = new ArrayList<T>();
}

/*
* 最小堆的向下调整算法
*
* 注:数组实现的堆中,第N个节点的左孩子的索引值是(2N+1),右孩子的索引是(2N+2)。
*
* 参数说明:
*     start -- 被下调节点的起始位置(一般为0,表示从第1个开始)
*     end   -- 截至范围(一般为数组中最后一个元素的索引)
*/
protected void filterdown(int start, int end) {
int c = start;          // 当前(current)节点的位置
int l = 2*c + 1;     // 左(left)孩子的位置
T tmp = mHeap.get(c);    // 当前(current)节点的大小

while(l <= end) {
int cmp = mHeap.get(l).compareTo(mHeap.get(l+1));
// "l"是左孩子,"l+1"是右孩子
if(l < end && cmp>0)
l++;        // 左右两孩子中选择较小者,即mHeap[l+1]

cmp = tmp.compareTo(mHeap.get(l));
if(cmp <= 0)
break;        //调整结束
else {
mHeap.set(c, mHeap.get(l));
c = l;
l = 2*l + 1;
}
}
mHeap.set(c, tmp);
}

/*
* 最小堆的删除
*
* 返回值:
*     成功,返回被删除的值
*     失败,返回null
*/
public int remove(T data) {
// 如果"堆"已空,则返回-1
if(mHeap.isEmpty() == true)
return -1;

// 获取data在数组中的索引
int index = mHeap.indexOf(data);
if (index==-1)
return -1;

int size = mHeap.size();
mHeap.set(index, mHeap.get(size-1));// 用最后元素填补
mHeap.remove(size - 1);                // 删除最后的元素

if (mHeap.size() > 1)
filterdown(index, mHeap.size()-1);    // 从index号位置开始自上向下调整为最小堆

return 0;
}

/*
* 最小堆的向上调整算法(从start开始向上直到0,调整堆)
*
* 注:数组实现的堆中,第N个节点的左孩子的索引值是(2N+1),右孩子的索引是(2N+2)。
*
* 参数说明:
*     start -- 被上调节点的起始位置(一般为数组中最后一个元素的索引)
*/
protected void filterup(int start) {
int c = start;            // 当前节点(current)的位置
int p = (c-1)/2;        // 父(parent)结点的位置
T tmp = mHeap.get(c);        // 当前节点(current)的大小

while(c > 0) {
int cmp = mHeap.get(p).compareTo(tmp);
if(cmp <= 0)
break;
else {
mHeap.set(c, mHeap.get(p));
c = p;
p = (p-1)/2;
}
}
mHeap.set(c, tmp);
}

/*
* 将data插入到二叉堆中
*/
public void insert(T data) {
int size = mHeap.size();

mHeap.add(data);    // 将"数组"插在表尾
filterup(size);        // 向上调整堆
}

public String toString() {
StringBuilder sb = new StringBuilder();
for (int i=0; i<mHeap.size(); i++)
sb.append(mHeap.get(i) +" ");

return sb.toString();
}

public static void main(String[] args) {
int i;
int a[] = {80, 40, 30, 60, 90, 70, 10, 50, 20};
MinHeap<Integer> tree=new MinHeap<Integer>();

System.out.printf("== 依次添加: ");
for(i=0; i<a.length; i++) {
System.out.printf("%d ", a[i]);
tree.insert(a[i]);
}

System.out.printf("\n== 最 小 堆: %s", tree);

i=15;
tree.insert(i);
System.out.printf("\n== 添加元素: %d", i);
System.out.printf("\n== 最 小 堆: %s", tree);

i=10;
tree.remove(i);
System.out.printf("\n== 删除元素: %d", i);
System.out.printf("\n== 最 小 堆: %s", tree);
System.out.printf("\n");
}
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: