您的位置:首页 > 编程语言 > Java开发

二项堆(三)之 Java的实现

2014-04-16 09:25 1646 查看

 

概要

前面分别通过C和C++实现了二项堆,本章给出二项堆的Java版本。还是那句老话,三种实现的原理一样,择其一了解即可。

目录
1. 二项树的介绍
2. 二项堆的介绍
3. 二项堆的基本操作
4. 二项堆的Java实现(完整源码)
5. 二项堆的Java测试程序

转载请注明出处:https://www.geek-share.com/detail/2608611503.html

更多内容:数据结构与算法系列 目录

(01) 二项堆(一)之 图文解析 和 C语言的实现
(02) 二项堆(二)之 C++的实现
(03) 二项堆(二)之 Java的实现

 

二项树的介绍

二项树的定义

二项堆是二项树的集合。在了解二项堆之前,先对二项树进行介绍。

二项树是一种递归定义的有序树。它的递归定义如下:
(01) 二项树B0只有一个结点;
(02) 二项树Bk由两棵二项树B(k-1)组成的,其中一棵树是另一棵树根的最左孩子。
如下图所示:

/*
* 将h1, h2中的根表合并成一个按度数递增的链表,返回合并后的根节点
*/
private BinomialNode<T> merge(BinomialNode<T> h1, BinomialNode<T> h2) {
if (h1 == null) return h2;
if (h2 == null) return h1;

// root是新堆的根,h3用来遍历h1和h3的。
BinomialNode<T> pre_h3, h3, root=null;

pre_h3 = null;
//整个while,h1, h2, pre_h3, h3都在往后顺移
while ((h1!=null) && (h2!=null)) {

if (h1.degree <= h2.degree) {
h3 = h1;
h1 = h1.next;
} else {
h3 = h2;
h2 = h2.next;
}

if (pre_h3 == null) {
pre_h3 = h3;
root = h3;
} else {
pre_h3.next = h3;
pre_h3 = h3;
}

if (h1 != null) {
h3.next = h1;
} else {
h3.next = h2;
}
}
return root;
}
View Code link()代码(Java)

/*
* 合并两个二项堆:将child合并到root中
*/
private void link(BinomialNode<T> child, BinomialNode<T> root) {
child.parent = root;
child.next   = root.child;
root.child = child;
root.degree++;
}
View Code

合并操作代码(Java)

/*
* 合并二项堆:将h1, h2合并成一个堆,并返回合并后的堆
*/
private BinomialNode<T> union(BinomialNode<T> h1, BinomialNode<T> h2) {
BinomialNode<T> root;

// 将h1, h2中的根表合并成一个按度数递增的链表root
root = merge(h1, h2);
if (root == null)
return null;

BinomialNode<T> prev_x = null;
BinomialNode<T> x      = root;
BinomialNode<T> next_x = x.next;
while (next_x != null) {

if (   (x.degree != next_x.degree)
|| ((next_x.next != null) && (next_x.degree == next_x.next.degree))) {
// Case 1: x.degree != next_x.degree
// Case 2: x.degree == next_x.degree == next_x.next.degree
prev_x = x;
x = next_x;
} else if (x.key.compareTo(next_x.key) <= 0) {
// Case 3: x.degree == next_x.degree != next_x.next.degree
//      && x.key    <= next_x.key
x.next = next_x.next;
link(next_x, x);
} else {
// Case 4: x.degree == next_x.degree != next_x.next.degree
//      && x.key    >  next_x.key
if (prev_x == null) {
root = next_x;
} else {
prev_x.next = next_x;
}
link(x, next_x);
x = next_x;
}
next_x = x.next;
}

return root;
}

/*
* 将二项堆other合并到当前堆中
*/
public void union(BinomialHeap<T> other) {
if (other!=null && other.mRoot!=null)
mRoot = union(mRoot, other.mRoot);
}

合并函数combine(h1, h2)的作用是将h1和h2合并,并返回合并后的二项堆。在combine(h1, h2)中,涉及到了两个函数merge(h1, h2)和link(child, root)。
merge(h1, h2)就是我们前面所说的"两个二项堆的根链表合并成一个链表,合并后的新链表按照'节点的度数'单调递增排序"。
link(child, root)则是为了合并操作的辅助函数,它的作用是将"二项堆child的根节点"设为"二项堆root的左孩子",从而将child整合到root中去。

        在combine(h1, h2)中对h1和h2进行合并时;首先通过 merge(h1, h2) 将h1和h2的根链表合并成一个"按节点的度数单调递增"的链表;然后进入while循环,对合并得到的新链表进行遍历,将新链表中"根节点度数相同的二项树"连接起来,直到所有根节点度数都不相同为止。在将新联表中"根节点度数相同的二项树"连接起来时,可以将被连接的情况概括为4种。

x是根链表的当前节点,next_x是x的下一个(兄弟)节点。
Case 1: x->degree != next_x->degree
             即,"当前节点的度数"与"下一个节点的度数"相等时。此时,不需要执行任何操作,继续查看后面的节点。
Case 2: x->degree == next_x->degree == next_x->next->degree
             即,"当前节点的度数"、"下一个节点的度数"和"下下一个节点的度数"都相等时。此时,暂时不执行任何操作,还是继续查看后面的节点。实际上,这里是将"下一个节点"和"下下一个节点"等到后面再进行整合连接。
Case 3: x->degree == next_x->degree != next_x->next->degree
        && x->key <= next_x->key
             即,"当前节点的度数"与"下一个节点的度数"相等,并且"当前节点的键值"<="下一个节点的度数"。此时,将"下一个节点(对应的二项树)"作为"当前节点(对应的二项树)的左孩子"。
Case 4: x->degree == next_x->degree != next_x->next->degree
        && x->key > next_x->key
             即,"当前节点的度数"与"下一个节点的度数"相等,并且"当前节点的键值">"下一个节点的度数"。此时,将"当前节点(对应的二项树)"作为"下一个节点(对应的二项树)的左孩子"。


下面通过示意图来对合并操作进行说明。

/**
* Java 语言: 二项堆
*
* @author skywang
* @date 2014/04/03
*/

public class BinomialHeap<T extends Comparable<T>> {

private BinomialNode<T> mRoot;    // 根结点

private class BinomialNode<T extends Comparable<T>> {
T key;                // 关键字(键值)
int degree;            // 度数
BinomialNode<T> child;    // 左孩子
BinomialNode<T> parent;    // 父节点
BinomialNode<T> next;    // 兄弟节点

public BinomialNode(T key) {
this.key = key;
this.degree = 0;
this.child = null;
this.parent = null;
this.next = null;
}

public String toString() {
return "key:"+key;
}
}

public BinomialHeap() {
mRoot = null;
}

/*
* 获取二项堆中的最小节点的键值
*/
public T minimum() {
if (mRoot==null)
return null;

BinomialNode<T> x, prev_x;    // x是用来遍历的当前节点
BinomialNode<T> y, prev_y;    // y是最小节点

prev_x  = mRoot;
x       = mRoot.next;
prev_y  = null;
y       = mRoot;
// 找到最小节点
while (x != null) {
if (x.key.compareTo(y.key) < 0) {
y = x;
prev_y = prev_x;
}
prev_x = x;
x = x.next;
}

return y.key;
}

/*
* 合并两个二项堆:将child合并到root中
*/
private void link(BinomialNode<T> child, BinomialNode<T> root) {
child.parent = root;
child.next   = root.child;
root.child = child;
root.degree++;
}

/*
* 将h1, h2中的根表合并成一个按度数递增的链表,返回合并后的根节点
*/
private BinomialNode<T> merge(BinomialNode<T> h1, BinomialNode<T> h2) {
if (h1 == null) return h2;
if (h2 == null) return h1;

// root是新堆的根,h3用来遍历h1和h3的。
BinomialNode<T> pre_h3, h3, root=null;

pre_h3 = null;
//整个while,h1, h2, pre_h3, h3都在往后顺移
while ((h1!=null) && (h2!=null)) {

if (h1.degree <= h2.degree) {
h3 = h1;
h1 = h1.next;
} else {
h3 = h2;
h2 = h2.next;
}

if (pre_h3 == null) {
pre_h3 = h3;
root = h3;
} else {
pre_h3.next = h3;
pre_h3 = h3;
}

if (h1 != null) {
h3.next = h1;
} else {
h3.next = h2;
}
}
return root;
}

/*
* 合并二项堆:将h1, h2合并成一个堆,并返回合并后的堆
*/
private BinomialNode<T> union(BinomialNode<T> h1, BinomialNode<T> h2) {
BinomialNode<T> root;

// 将h1, h2中的根表合并成一个按度数递增的链表root
root = merge(h1, h2);
if (root == null)
return null;

BinomialNode<T> prev_x = null;
BinomialNode<T> x      = root;
BinomialNode<T> next_x = x.next;
while (next_x != null) {

if (   (x.degree != next_x.degree)
|| ((next_x.next != null) && (next_x.degree == next_x.next.degree))) {
// Case 1: x.degree != next_x.degree
// Case 2: x.degree == next_x.degree == next_x.next.degree
prev_x = x;
x = next_x;
} else if (x.key.compareTo(next_x.key) <= 0) {
// Case 3: x.degree == next_x.degree != next_x.next.degree
//      && x.key    <= next_x.key
x.next = next_x.next;
link(next_x, x);
} else {
// Case 4: x.degree == next_x.degree != next_x.next.degree
//      && x.key    >  next_x.key
if (prev_x == null) {
root = next_x;
} else {
prev_x.next = next_x;
}
link(x, next_x);
x = next_x;
}
next_x = x.next;
}

return root;
}

/*
* 将二项堆other合并到当前堆中
*/
public void union(BinomialHeap<T> other) {
if (other!=null && other.mRoot!=null)
mRoot = union(mRoot, other.mRoot);
}

/*
* 新建key对应的节点,并将其插入到二项堆中。
*/
public void insert(T key) {
BinomialNode<T> node;

// 禁止插入相同的键值
if (contains(key)==true) {
System.out.printf("insert failed: the key(%s) is existed already!\n", key);
return ;
}

node = new BinomialNode<T>(key);
if (node==null)
return ;

mRoot = union(mRoot, node);
}

/*
* 反转二项堆root,并返回反转后的根节点
*/
private BinomialNode<T> reverse(BinomialNode<T> root) {
BinomialNode<T> next;
BinomialNode<T> tail = null;

if (root==null)
return root;

root.parent = null;
while (root.next!=null) {
next         = root.next;
root.next    = tail;
tail         = root;
root         = next;
root.parent  = null;
}
root.next = tail;

return root;
}

/*
* 移除二项堆root中的最小节点,并返回删除节点后的二项树
*/
private BinomialNode<T> extractMinimum(BinomialNode<T> root) {
if (root==null)
return root;

BinomialNode<T> x, prev_x;    // x是用来遍历的当前节点
BinomialNode<T> y, prev_y;    // y是最小节点

prev_x  = root;
x       = root.next;
prev_y = null;
y      = root;
// 找到最小节点
while (x != null) {
if (x.key.compareTo(y.key) < 0) {
y = x;
prev_y = prev_x;
}
prev_x = x;
x = x.next;
}

if (prev_y == null)    // root的根节点就是最小根节点
root = root.next;
else                // root的根节点不是最小根节点
prev_y.next = y.next;

// 反转最小节点的左孩子,得到最小堆child;
// 这样,就使得最小节点所在二项树的孩子们都脱离出来成为一棵独立的二项树(不包括最小节点)
BinomialNode<T> child = reverse(y.child);
// 将"删除最小节点的二项堆child"和"root"进行合并。
root = union(root, child);

// help GC
y = null;

return root;
}

public void extractMinimum() {
mRoot = extractMinimum(mRoot);
}

/*
* 减少关键字的值:将二项堆中的节点node的键值减小为key。
*/
private void decreaseKey(BinomialNode<T> node, T key) {
if(key.compareTo(node.key)>=0 || contains(key)==true) {
System.out.println("decrease failed: the new key("+key+") is existed already, or is no smaller than current key("+node.key+")");
return ;
}
node.key = key;

BinomialNode<T> child, parent;
child = node;
parent = node.parent;
while(parent != null && child.key.compareTo(parent.key)<0) {
// 交换parent和child的数据
T tmp = parent.key;
parent.key = child.key;
child.key = tmp;

child = parent;
parent = child.parent;
}
}

/*
* 增加关键字的值:将二项堆中的节点node的键值增加为key。
*/
private void increaseKey(BinomialNode<T> node, T key) {
if(key.compareTo(node.key)<=0 || contains(key)==true) {
System.out.println("increase failed: the new key("+key+") is existed already, or is no greater than current key("+node.key+")");
return ;
}
node.key = key;

BinomialNode<T> cur = node;
BinomialNode<T> child = cur.child;
while (child != null) {

if(cur.key.compareTo(child.key) > 0) {
// 如果"当前节点" < "它的左孩子",
// 则在"它的孩子中(左孩子 和 左孩子的兄弟)"中,找出最小的节点;
// 然后将"最小节点的值" 和 "当前节点的值"进行互换
BinomialNode<T> least = child;    // least是child和它的兄弟中的最小节点
while(child.next != null) {
if (least.key.compareTo(child.next.key) > 0)
least = child.next;
child = child.next;
}
// 交换最小节点和当前节点的值
T tmp = least.key;
least.key = cur.key;
cur.key = tmp;

// 交换数据之后,再对"原最小节点"进行调整,使它满足最小堆的性质:父节点 <= 子节点
cur = least;
child = cur.child;
} else {
child = child.next;
}
}
}

/*
* 更新二项堆的节点node的键值为key
*/
private void updateKey(BinomialNode<T> node, T key) {
if (node == null)
return ;

int cmp = key.compareTo(node.key);
if(cmp < 0)                            // key < node.key
decreaseKey(node, key);
else if(cmp > 0)                    // key > node.key
increaseKey(node, key);
else
System.out.println("No need to update!!!");
}

/*
* 将二项堆中键值oldkey更新为newkey
*/
public void update(T oldkey, T newkey) {
BinomialNode<T> node;

node = search(mRoot, oldkey);
if (node != null)
updateKey(node, newkey);
}

/*
* 查找:在二项堆中查找键值为key的节点
*/
private BinomialNode<T> search(BinomialNode<T> root, T key) {
BinomialNode<T> child;
BinomialNode<T> parent = root;

parent = root;
while (parent != null) {
if (parent.key.compareTo(key) == 0)
return parent;
else {
if((child = search(parent.child, key)) != null)
return child;
parent = parent.next;
}
}

return null;
}

/*
* 二项堆中是否包含键值key
*/
public boolean contains(T key) {
return search(mRoot, key)!=null ? true : false;
}

/*
* 删除节点:删除键值为key的节点
*/
private BinomialNode<T> remove(BinomialNode<T> root, T key) {
if (root==null)
return root;

BinomialNode<T> node;

// 查找键值为key的节点
if ((node = search(root, key)) == null)
return root;

// 将被删除的节点的数据数据上移到它所在的二项树的根节点
BinomialNode<T> parent = node.parent;
while (parent != null) {
// 交换数据
T tmp = node.key;
node.key = parent.key;
parent.key = tmp;

// 下一个父节点
node   = parent;
parent = node.parent;
}

// 找到node的前一个根节点(prev)
BinomialNode<T> prev = null;
BinomialNode<T> pos  = root;
while (pos != node) {
prev = pos;
pos  = pos.next;
}
// 移除node节点
if (prev!=null)
prev.next = node.next;
else
root = node.next;

root = union(root, reverse(node.child));

// help GC
node = null;

return root;
}

public void remove(T key) {
mRoot = remove(mRoot, key);
}

/*
* 打印"二项堆"
*
* 参数说明:
*     node       -- 当前节点
*     prev       -- 当前节点的前一个节点(父节点or兄弟节点)
*     direction  --  1,表示当前节点是一个左孩子;
*                    2,表示当前节点是一个兄弟节点。
*/
private void print(BinomialNode<T> node, BinomialNode<T> prev, int direction) {
while(node != null)
{
if(direction==1)    // node是根节点
System.out.printf("\t%2d(%d) is %2d's child\n", node.key, node.degree, prev.key);
else                // node是分支节点
System.out.printf("\t%2d(%d) is %2d's next\n", node.key, node.degree, prev.key);

if (node.child != null)
print(node.child, node, 1);

// 兄弟节点
prev = node;
node = node.next;
direction = 2;
}
}

public void print() {
if (mRoot == null)
return ;

BinomialNode<T> p = mRoot;
System.out.printf("== 二项堆( ");
while (p != null) {
System.out.printf("B%d ", p.degree);
p = p.next;
}
System.out.printf(")的详细信息:\n");

int i=0;
p = mRoot;
while (p != null) {
i++;
System.out.printf("%d. 二项树B%d: \n", i, p.degree);
System.out.printf("\t%2d(%d) is root\n", p.key, p.degree);

print(p.child, p, 1);
p = p.next;
}
System.out.printf("\n");
}
}
View Code 二项堆的测试程序(Main.java)

/**
* Java 语言: 二项堆
*
* @author skywang
* @date 2014/03/31
*/

public class Main {

private static final boolean DEBUG = false;

// 共7个 = 1+2+4
private static int a[] = {12,  7, 25, 15, 28, 33, 41};
// 共13个 = 1+4+8
private static int b[] = {18, 35, 20, 42,  9,
31, 23,  6, 48, 11,
24, 52, 13 };

// 验证"二项堆的插入操作"
public static void testInsert() {
BinomialHeap<Integer> ha=new BinomialHeap<Integer>();

// 二项堆ha
System.out.printf("== 二项堆(ha)中依次添加: ");
for(int i=0; i<a.length; i++) {
System.out.printf("%d ", a[i]);
ha.insert(a[i]);
}
System.out.printf("\n");
System.out.printf("== 二项堆(ha)的详细信息: \n");
ha.print();
}

// 验证"二项堆的合并操作"
public static void testUnion() {
BinomialHeap<Integer> ha=new BinomialHeap<Integer>();
BinomialHeap<Integer> hb=new BinomialHeap<Integer>();

// 二项堆ha
System.out.printf("== 二项堆(ha)中依次添加: ");
for(int i=0; i<a.length; i++) {
System.out.printf("%d ", a[i]);
ha.insert(a[i]);
}
System.out.printf("\n");
System.out.printf("== 二项堆(ha)的详细信息: \n");
ha.print();

// 二项堆hb
System.out.printf("== 二项堆(hb)中依次添加: ");
for(int i=0; i<b.length; i++) {
System.out.printf("%d ", b[i]);
hb.insert(b[i]);
}
System.out.printf("\n");
// 打印二项堆hb
System.out.printf("== 二项堆(hb)的详细信息: \n");
hb.print();

// 将"二项堆hb"合并到"二项堆ha"中。
ha.union(hb);
// 打印二项堆ha的详细信息
System.out.printf("== 合并ha和hb后的详细信息:\n");
ha.print();
}

// 验证"二项堆的删除操作"
public static void testDelete() {
BinomialHeap<Integer> hb=new BinomialHeap<Integer>();

// 二项堆hb
System.out.printf("== 二项堆(hb)中依次添加: ");
for(int i=0; i<b.length; i++) {
System.out.printf("%d ", b[i]);
hb.insert(b[i]);
}
System.out.printf("\n");
// 打印二项堆hb
System.out.printf("== 二项堆(hb)的详细信息: \n");
hb.print();

// 将"二项堆hb"合并到"二项堆ha"中。
hb.remove(20);
System.out.printf("== 删除节点20后的详细信息: \n");
hb.print();
}

// 验证"二项堆的更新(减少)操作"
public static void testDecrease() {
BinomialHeap<Integer> hb=new BinomialHeap<Integer>();

// 二项堆hb
System.out.printf("== 二项堆(hb)中依次添加: ");
for(int i=0; i<b.length; i++) {
System.out.printf("%d ", b[i]);
hb.insert(b[i]);
}
System.out.printf("\n");
// 打印二项堆hb
System.out.printf("== 二项堆(hb)的详细信息: \n");
hb.print();

// 将节点20更新为2
hb.update(20, 2);
System.out.printf("== 更新节点20->2后的详细信息: \n");
hb.print();
}

// 验证"二项堆的更新(减少)操作"
public static void testIncrease() {
BinomialHeap<Integer> hb=new BinomialHeap<Integer>();

// 二项堆hb
System.out.printf("== 二项堆(hb)中依次添加: ");
for(int i=0; i<b.length; i++) {
System.out.printf("%d ", b[i]);
hb.insert(b[i]);
}
System.out.printf("\n");
// 打印二项堆hb
System.out.printf("== 二项堆(hb)的详细信息: \n");
hb.print();

// 将节点6更新为60
hb.update(6, 60);
System.out.printf("== 更新节点6->60后的详细信息: \n");
hb.print();
}

public static void main(String[] args) {
// 1. 验证"二项堆的插入操作"
testInsert();
// 2. 验证"二项堆的合并操作"
//testUnion();
// 3. 验证"二项堆的删除操作"
//testDelete();
// 4. 验证"二项堆的更新(减少)操作"
//testDecrease();
// 5. 验证"二项堆的更新(增加)操作"
//testIncrease();
}
}
View Code

 

二项堆的Java测试程序

二项堆的测试程序包括了五部分,分别是"插入"、"删除"、"增加"、"减少"、"合并"这5种功能的测试代码。默认是运行的"插入"功能代码,你可以根据自己的需要来对相应的功能进行验证!

下面是插入功能运行结果:

== 二项堆(ha)中依次添加: 12 7 25 15 28 33 41
== 二项堆(ha)的详细信息:
== 二项堆( B0 B1 B2 )的详细信息:
1. 二项树B0:
41(0) is root
2. 二项树B1:
28(1) is root
33(0) is 28's child
3. 二项树B2:
7(2) is root
15(1) is  7's child
25(0) is 15's child
12(0) is 15's next

 

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