一)树堆简介
实现:Treap=Tree+Heap
简介:Treap是一棵二叉排序树,它的左子树和右子树分别是一个Treap,和一般的二叉排序树不同的是,Treap记录一个额外的数据,就是优先级。Treap在以关键码构成二叉排序树的同时,还满足堆的性质(在这里我们假设节点的优先级大于该节点的孩子的优先级)。但是这里要注意的是Treap和二叉堆有一点不同,就是二叉堆必须是完全二叉树,而Treap可以并不一定是。
备注:树堆包含最大堆和最小堆两种,只是在旋转和比较大小的时候相反。
二)最小堆
第一步:定义一个二叉树结构,多声明一个int的优先级别参数,该参数通过随机器生成。
/**
* 树堆(最小堆)
* @author ouyangjun
*/
public class TreapTree<T extends Comparable<T>> {
// 二叉树树堆结构
static class Node<T> {
T value; // 节点的值
int priority; // 节点的优先级,用于满足堆的性质
Node<T> left; // 左节点
Node<T> right; // 右节点
Node (T value) {
this(null, null, value);
}
Node (Node<T> left, Node<T> right, T value) {
this.left = left;
this.right = right;
this.value = value;
this.priority = random();
}
// 随机生成器,用于堆处理,需保证数据尽量不重复
private Random random=new Random();
private int random() {
int num = random.nextInt();
return (int)(num*48271L % Integer.MAX_VALUE);
}
}
private Node<T> root; // 根节点
public Node<T> getRoot() {
return root;
}
}
第二步:旋转方式(AVL中的左左旋转和右右旋转)
// 左左旋转
private Node<T> leftleft(Node<T> node) {
Node<T> newNode = node.left; // 把根节点下的左边节点作为根节点
node.left = newNode.right; // 把newNode下的右节点作为node的左节点
newNode.right = node; // 把原先的根节点作为现有根节点的右节点
// 返回
return newNode;
}
// 右右旋转
private Node<T> rightright(Node<T> node) {
Node<T> newNode = node.right; // 把根节点下的右边节点作为根节点
node.right = newNode.left; // 把newNode下的左节点作为node的右节点
newNode.left = node; // 把原先的根节点作为现有根节点的左节点
// 返回
return newNode;
}
第三步:辅助方法
// 查找节点是否存在
private boolean search(T value) {
if (value == null) {
return false;
}
boolean isExist = false;
Node<T> rootNode = root;
while (rootNode != null) {
int num = value.compareTo(rootNode.value);
if (num < 0) {
rootNode = rootNode.left;
} else if (num > 0) {
rootNode = rootNode.right;
} else {
isExist = true;
break;
}
}
return isExist;
}
// 前序遍历: 根节点 ==> 左节点 ==> 右节点
public void preNode(Node<T> node) {
if (node != null) {
System.out.print(node.value + "(" + node.priority + ")" + "\t");
preNode(node.left);
preNode(node.right);
}
}
// 中序遍历: 左节点 ==> 根节点 ==> 右节点
public void inNode(Node<T> node) {
if (node != null) {
inNode(node.left);
System.out.print(node.value + "(" + node.priority + ")" + "\t");
inNode(node.right);
}
}
// 后序遍历: 左节点 ==> 右节点 ==> 根节点
public void nextNode(Node<T> node) {
if (node != null) {
nextNode(node.left);
nextNode(node.right);
System.out.print(node.value + "(" + node.priority + ")" + "\t");
}
}
第四步:新增节点
场景一:从根节点开始插入;
场景二:如果要插入的值小于等于当前节点的值,在当前节点的左子树中插入,插入后如果左子节点的修正值小于当前节点的修正值,对当前节点进行右旋;
场景三:如果要插入的值大于当前节点的值,在当前节点的右子树中插入,插入后如果右子节点的修正值小于当前节点的修正值,对当前节点进行左旋;
场景四:如果当前节点为空节点,在此建立新的节点,该节点的值为要插入的值,左右子树为空,插入成功。
/**
* 添加节点
*/
public void add(T value) {
if (value == null || search(value)) {
return;
}
Node<T> rootNode = add(root, value);
root = rootNode;
}
private Node<T> add(Node<T> node, T value) {
if (node == null) {
// 新增节点
Node<T> newNode = new Node<T>(null, null, value);
return newNode;
} else {
int num = value.compareTo(node.value);
if (num < 0) {
node.left = add(node.left, value);
// 当满足情况的时候,对其进行旋转操作
if (node.left.priority < node.priority) {
node= leftleft(node);
}
} else if (num > 0) {
node.right = add(node.right, value);
// 当满足情况的时候,对其进行旋转操作
if (node.right.priority < node.priority) {
node= rightright(node);
}
}
}
return node;
}
第五步:删除节点
// 删除节点
public void remove(T value) {
if (value == null || !search(value)) {
return;
}
Node<T> rootNode = remove(root, value);
root = rootNode;
}
/**
* 删除对应的节点
* @param node 开始进行比较的节点
* @param key 进行删除的节点的关键字
* @return 其子树的根节点
*/
private Node<T> remove(Node<T> node, T value) {
if (node == null) {
return null;
}
int num = value.compareTo(node.value);
if (num < 0) {
node.left = remove(node.left, value);
} else if(num > 0) {
node.right = remove(node.right, value);
} else {
// 当存在左右孩子节点的时候
if (node.left != null && node.right != null) {
// 如果左孩子优先级低就右旋
if (node.left.priority < node.right.priority) {
node = leftleft(node);
} else {
node = rightright(node);
}
// 旋转后继续进行删除操作
node = remove(node, value);
} else {
// 当其为根节点的时候
if (node.left == null && node.right == null) {
return null;
}
// 当其为单分支树的时候
node = node.left == null ? node.right : node.left;
}
}
return node;
}
第六步:main方法测试
public static void main(String[] args) {
TreapTree<Integer> treap = new TreapTree<Integer>();
treap.add(8);
treap.add(3);
treap.add(20);
treap.add(16);
treap.add(4);
treap.remove(3);
System.out.println("前序遍历结果为:");
treap.preNode(treap.getRoot());
System.out.println("\n中序遍历结果为:");
treap.inNode(treap.getRoot());
System.out.println("\n后序遍历结果为:");
treap.nextNode(treap.getRoot());
}
打印效果图:
备注:由于树堆的优先级是通过随机器生成,所以每指向一遍,排列的方式可能不一致。可固定指定优先级别。
识别二维码关注个人微信公众号
本章完结,待续,欢迎转载!
本文说明:该文章属于原创,如需转载,请标明文章转载来源!