伸展二叉树

一)伸展二叉树简介

规则1、和二叉搜索树性质一样,比节点小的值排列在左边,比节点大的值排列在右边。

规则2、当某个节点被访问时,伸展树会通过旋转使该节点成为树根。方便多次查找。

 

二)伸展二叉树(采用自顶向下旋转)

第一步:定义伸展二叉树结构

/**
 * 伸展二叉树
 * @author ouyangjun
 */
public class SplayTree<T extends Comparable<T>> {
	
    // 伸展二叉树结构
    static class Node<T> {
        Node<T> left;  // 左节点
        Node<T> right; // 右节点
        T value;       // 节点值(关键字)
		
        Node (Node<T> left, Node<T> right, T value) {
            this.left = left;
            this.right = right;
            this.value = value;
        }
    }
	
    private Node<T> root; // 根节点

    public Node<T> getRoot() {
        return root;
    }
}

 

第二步:旋转,包括左左旋转和右右旋转

// 左左旋转
private Node<T> leftleft(Node<T> node) {
    Node<T> newNode = node.left; // 把node节点下的左边节点先用一个临时变量存储
    node.left = newNode.right;   // 把newNode下的右节点作为node的左节点
    newNode.right = node;        // 把node节点作为现有newNode节点下的右节点
		
    // 返回
    return newNode;
}
	
// 右右旋转
private Node<T> rightright(Node<T> node) {
    Node<T> newNode = node.right; // 把node节点下的右边节点先用一个临时变量存储
    node.right = newNode.left;    // 把newNode下的左节点作为node的右节点
    newNode.left = node;          // 把node节点作为newNode节点下的左节点
		
    // 返回
    return newNode;
}
	
// 旋转
private Node<T> splay(Node<T> node, T value) {
    if (value == null) {
        return null;
    }
    Node<T> splayNode = new Node<T>(null, null, value);
		
    // 调用旋转节点方法
    return splay(node, splayNode);
}
	
private Node<T> splay(Node<T> node, Node<T> splayNode) {
    if (node == null || splayNode == null) {
        // 返回根节点
        return root;
    }
		
    Node<T> nullNode = new Node<T>(null, null, null); // 记录中树的节点
    Node<T> leftNode = nullNode; // 记录小于node的节点
    Node<T> rightNode = nullNode; // 记录大于node的节点
		
    // 判断
    while (node != null) {
        // 判断
        int num = splayNode.value.compareTo(node.value); // 把value和node的value比较
        if (num < 0) { // 在左边
            if (node.left == null) {
                break;
            }
					
            // splayNode小于node
            if (splayNode.value.compareTo(node.left.value) < 0) {
                // 进行了AVL中的左左旋转
                node = leftleft(node);
                if (node.left == null) {
                    break;
                }
            }
					
            leftNode.left = node;
            leftNode = node;
            node = node.left; // 对节点进行了右旋转
        } else if (num > 0) { // 在右边
            if (node.right == null) {
                break;
            }
					
            // splayNode大于node
            if (splayNode.value.compareTo(node.right.value) > 0) {
                // 进行了AVL中的右右旋转
                node = rightright(node);
                if (node.right == null) {
                    break;
                }
            }
	
            rightNode.right = node;
            rightNode = node;
            node = node.right; // 对节点进行了左旋转
        } else {
            // 相等, 从二叉树中找到开始旋转的节点了, 直接跳出
            break;
        }
    }
		
    // 把leftNode节点和rightNode节点合并到node
    leftNode.left = node.right;
    rightNode.right = node.left;
    node.left = nullNode.right;
    node.right = nullNode.left;
		
    // 重新存储节点
    root = node;
    return node;
}

 

第三步:新增节点

// 新增节点
public Node<T> add(T value) {
    if (value == null) {
        return null;
    }
    Node<T> newNode = new Node<T>(null, null, value);
		
    // 根节点
    Node<T> rootNode = add(root, newNode);
		
    // 调用旋转节点方法
    return splay(rootNode, newNode);
}
	
private Node<T> add(Node<T> node, Node<T> newNode) {
    Node<T> current = null; // 需要插入的节点
		
    // 判断节点是否为空
    while (node != null) {
        current = node;
			
        // 判断
        int num = newNode.value.compareTo(node.value); // 把value和treeNode的value比较
        if (num < 0) { // 在左边
            node = node.left;
        } else if (num > 0) { // 在右边
            node = node.right;
        } else { // 相等, 节点重复了, 直接返回
            return node;
        }
    }
		
    if (current == null) { // 如果没有找到插入节点,把新节点作为根节点
        root = newNode;
    } else {
        int num = newNode.value.compareTo(current.value);
        if (num < 0) { // 在左边新增
            current.left = newNode;
        } else if (num > 0) { // 在右边新增
            current.right = newNode;
        }
    }
		
    // 返回
    return root;
}

 

第四步:辅助方法

// 查找节点是否存在
private boolean search(Node<T> node, T value) {
    if (node != null) {
        int num = value.compareTo(node.value);
        if (num < 0) {
            return search(node.left, value);
        } else if(num > 0) {
            return search(node.right, value);
        } else {
            return true;
        }
    }
    return false;
}

/**
 * 前序遍历: 根节点 ==> 左节点 ==> 右节点
 * @param node
 */
public void preNode(Node<T> node) {
    if (node != null) {
        System.out.print(node.value + "\t");
			
        preNode(node.left);
			
        preNode(node.right);
    }
}
	
/**
 * 中序遍历: 左节点 ==> 根节点 ==> 右节点
 * @param node
 */
public void inNode(Node<T> node) {
    if (node != null) {
        inNode(node.left);
			
        System.out.print(node.value + "\t");
			
        inNode(node.right);
    }
}
	
/**
 * 后序遍历: 左节点 ==> 右节点 ==> 根节点
 * @param node
 */
public void nextNode(Node<T> node) {
    if (node != null) {
        nextNode(node.left);
			
        nextNode(node.right);
			
        System.out.print(node.value + "\t");
    }
}

 

第五步:删除节点

// 删除节点
public Node<T> remove(T value) {
    return remove(root, value);
}
	
private Node<T> remove(Node<T> node, T value) {
    if (node == null || value == null) {
        return null;
    }

    // 查找键值为key的节点,找不到的话直接返回。
    if (!search(node, value)) {
        return node;
    }
	    
    Node<T> x;

    // 将key对应的节点旋转为根节点。
    node = splay(node, value);

    if (node.left != null) {
        // 将"tree的前驱节点"旋转为根节点
        x = splay(node.left, value);
        // 移除tree节点
        x.right = node.right;
    } else {
        x = node.right;
    }
    node = null;

    // 重新保存根节点
    root = x;
    return x;
}

 

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

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值