树堆(最小堆)

一)树堆简介

​实现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());
}

打印效果图:

备注:由于树堆的优先级是通过随机器生成,所以每指向一遍,排列的方式可能不一致。可固定指定优先级别。

 

识别二维码关注个人微信公众号

本章完结,待续,欢迎转载!
 
本文说明:该文章属于原创,如需转载,请标明文章转载来源!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值