zoukankan      html  css  js  c++  java
  • 【数据结构】二叉排序树BST

    二叉排序树

    二叉排序树(Binary Sort Tree)也叫二叉搜索树(Binary Search Tree)

    二叉排序树本质上还是一个二叉树,只不过在其上定义了一些规则:一个结点的左子树中所有的结点不大于该结点的值,而其右子树中的所有结点不小于该结点的值。由此规则可得BST中序遍历是有序的。

    BST中定义的操作有:

    minNode:某个子树中的关键字最小的结点

    maxNode:某个子树中的关键字最大的结点

    search:搜索某个关键字

    predecessor:某个节点的前驱

    successor:某个节点的后继

    insert:在BST中插入一个元素

    delete:在BST中删除一个元素

    研究二叉排序树最主要的就是上述操作在BST中的时间复杂度为O(h),h为树高。

    相关算法

    1.某棵子树中关键字最小的结点
        由BST的性质,沿着该结点的左分支往下走到最后一个结点

    public BinaryTreeNode minNode(BinaryTreeNode node) {
    		while (node.lchild != null) {
    			node = node.lchild;
    		}
    		return node;
    	}

    2.某棵子树中关键字最大的结点
        由BST的性质,沿着该结点的右分支往下走到最后一个结点

    public BinaryTreeNode maxNode(BinaryTreeNode node) {
    		while (node.rchild != null) {
    			node = node.rchild;
    		}
    		return node;
    	}

    3.搜索
        BST中的搜索类似于折半查找,因为BST的中序遍历就是一个有序数组。

    public BinaryTreeNode search(BinaryTreeNode root, int e) {
    		if (e == root.data || root == null)
    			return root;
    		if (e < root.data)
    			return search(root.lchild, e);
    		else
    			return search(root.rchild, e);
    	}

    4.某个结点的前驱
        如有左子树,那么返回左子树中的最小结点即可。
        无左子树,沿着父节点往上走,知道到达这样的结点:其父节点为空(到达根)或者作为某个结点的右子树。

    public BinaryTreeNode predecessor(BinaryTreeNode node) {
    		if (node.lchild != null)
    			return maxNode(node.lchild);
    		BinaryTreeNode parent = node.parent;
    		while (parent != null && parent.lchild == node) {
    			node = parent;
    			parent = node.parent;
    		}
    		return parent;
    	}
        
    5.某个结点的后继前驱的算法类似

    具体思路如下


    6.在BST中插入一个元素
        和查找类似,只是要注意保存好父结点,x从根开始下移,直到x为空,而此时y就是他的父结点。

    具体思路如下


    具体的实现代码为

    public void insert(BinaryTreeNode insertNode) { // O(lgn)
    		BinaryTreeNode p = root, parent = null;
    		while (p != null) {
    			parent = p; // 注意位置
    			if (insertNode.data < p.data)
    				p = p.lchild;
    			else
    				p = p.rchild;
    		} // 出循环时,p为null,parent为要插入在该节点下面
    		insertNode.parent = parent;
    		if (root == null) {
    			root = insertNode;
    		} else if (insertNode.data < parent.data) {
    			// 将节点插入到parent的左边
    			parent.lchild = insertNode;
    		} else {
    			parent.rchild = insertNode;
    		}
    	}

    插入结点的递归版本
        这里我将跳出递归的条件设置为root.lchild/rchild == null,好处就是直接在root后面添加孩子就行了,不用再额外保存父亲结点。

    public void insertRecur(BinaryTreeNode root, BinaryTreeNode insertNode) { 
    		// 递归终止条件
    		if (this.root == null) {
    			this.root = insertNode;
    			return;
    		} else if (insertNode.data < root.data && root.lchild == null) {
    			root.lchild = insertNode;
    			insertNode.parent = root;
    			return;
    		} else if (insertNode.data >= root.data && root.rchild == null) {
    			root.rchild = insertNode;
    			insertNode.parent = root;
    			return;
    		}
    		// 递归的往左右孩子插入
    		if (insertNode.data < root.data && root.lchild != null)
    			insertRecur(root.lchild,insertNode);
    		if (insertNode.data >= root.data && root.rchild != null) {
    			insertRecur(root.rchild,insertNode);
    		}
    	}
    同时,通过插入方法也可以创建BST。只需循环的往里面插入节点即可。


    7.在BST中删除一个元素 

        1.删除叶子结点----->仅需调整其父结点的指针
        2.只有左子树或者右子树
          2.1 只有左孩子:用它的左子树“代替”它。
          2.2 只有右孩子:用右子树“代替”它。
        3.既有左孩子又有右孩子:用它的左或右子树代替它,同时将它的右子树放在它的前驱的右子树位置。

    具体情况如下图



    注意上面提到的“代替”,代替并不简单的是node = node.lchild这样的一句赋值,实际上要包含以下操作:

        1.如果该结点是它父结点的左孩子,那么,将他的父结点的左孩子赋值为另一个结点

        2.如果该结点是它父结点的右孩子,那么,将他的父结点的右孩子赋值为另一个结点

        3.最后将用来代替的那个结点的父亲设置为被代替节点的父亲

        此外还要注意被替换结点没有父亲节点的情况,也就是删除根。

    下面是我最开始写出来的代码,其实看起来非常的混乱

    public void deleted(BinaryTreeNode node) {
    		if (node.lchild == null) { // 左子树为空,只需要移植右子树
    			// -------1.用该结点的右子树代替它-------
    			// node = node.rchild;
    			if (node.parent == null) {
    				root = node.rchild;
    			} else if (node == node.parent.lchild) {
    				node.parent.lchild = node.rchild;
    			} else {
    				node.parent.rchild = node.rchild;
    			}
    			if (node.rchild != null)
    				node.rchild.parent = node.parent;
    		} else if (node.rchild == null) {
    			// -------2.用该结点的左子树代替它-------
    			// node = node.lchild;
    			if (node.parent == null) {
    				root = node.lchild;
    			} else if (node == node.parent.lchild) {
    				node.parent.lchild = node.lchild;
    			} else {
    				node.parent.rchild = node.lchild;
    			}
    			if (node.lchild != null)
    				node.lchild.parent = node.parent;
    		} else {
    			BinaryTreeNode predecessorOfnode = predecessor(node);
    			// -------3.用该结点的右子树代替它-------
    			if (node.parent == null) {
    				root = node.lchild;
    			} else if (node.parent.lchild == node) {
    				node.parent.lchild = node.lchild;
    			} else if (node.parent.rchild == node) {
    				node.parent.rchild = node.lchild;
    			}
    			node.lchild.parent = node.parent;
    			node.rchild.parent = predecessorOfnode;
    
    
    			// -------4.将右子树赋值给它的前驱-------
    			predecessorOfnode.rchild = node.rchild;
    			node.rchild.parent = predecessorOfnode;
    		}
    	}
    上面的代码存在大量的重复,将其抽出来一个替换的方法
    // 用node2来替代node1
    private void replace(BinaryTreeNode node1, BinaryTreeNode node2){
    		// node2可以为null,这样就将删除叶子结点的情况包含在里面了
    		if (node1.parent == null) {
    			root = node2;
    		} else if (node1 == node1.parent.lchild) {
    			node1.parent.lchild = node2;
    		} else {
    			node1.parent.rchild = node2;
    		}
    		if (node2 != null)
    			node2.parent = node1.parent;
    	}
    于是删除的方法可以写为
    public void delete(BinaryTreeNode node) {
    		if (node.lchild == null) { // 左子树为空,只需要移植右子树
    			replace(node, node.rchild);
    		} else if (node.rchild == null) {
    			replace(node, node.lchild);
    		} else { 
    			BinaryTreeNode predecessorOfnode = predecessor(node);
    			replace(node, node.lchild);
    			
    			predecessorOfnode.rchild = node.rchild;
    			node.rchild.parent = predecessorOfnode;
    		}
    	}	


    BST的实现

    继承上一篇博客的BinaryTree类,这样就可以调用遍历算法了

    public class BinarySearchTree extends BinaryTree {
    	// 从数组创建二叉树
    	@Override
    	public void createTree(int[] array) { // 最多是O(nlgn)
    		// 从一个数组创建二叉搜索树
    		for (int i : array) {
    			insertRecur(root, new BinaryTreeNode(i));
    		}
    	}
    	// 某个子树的最小结点
    	public BinaryTreeNode minNode(BinaryTreeNode node) {
    		while (node.lchild != null) {
    			node = node.lchild;
    		}
    		return node;
    	}
    	
    	//某个子树的最大结点
    	public BinaryTreeNode maxNode(BinaryTreeNode node) {
    		while (node.rchild != null) {
    			node = node.rchild;
    		}
    		return node;
    	}
    
    	// 搜索关键字为e的结点,并返回该结点的引用
    	public BinaryTreeNode search(BinaryTreeNode root, int e) {
    		if (root == null || e == root.data) // 注意顺序
    			return root;
    		if (e < root.data)
    			return search(root.lchild, e);
    		else
    			return search(root.rchild, e);
    	}
    	
    	// 插入结点,递归版本
    	public void insertRecur(BinaryTreeNode root, BinaryTreeNode insertNode) { // O(lgn)
    		if (this.root == null) {
    			this.root = insertNode;
    			return;
    		} else if (insertNode.data < root.data && root.lchild == null) {
    			root.lchild = insertNode;
    			insertNode.parent = root;
    			return;
    		} else if (insertNode.data >= root.data && root.rchild == null) {
    			root.rchild = insertNode;
    			insertNode.parent = root;
    			return;
    		}
    		
    		if (insertNode.data < root.data && root.lchild != null)
    			insertRecur(root.lchild,insertNode);
    		if (insertNode.data >= root.data && root.rchild != null) {
    			insertRecur(root.rchild,insertNode);
    		}
    	}
    	
    	// 插入节点,非递归版本
    	public void insert(BinaryTreeNode insertNode) { // O(lgn)
    		BinaryTreeNode p = root, parent = null;
    		while (p != null) {
    			parent = p; // 注意位置
    			if (insertNode.data < p.data)
    				p = p.lchild;
    			else
    				p = p.rchild;
    		} // 出循环时,p为null,parent为要插入在该节点下面
    		insertNode.parent = parent;
    		if (root == null) {
    			root = insertNode;
    		} else if (insertNode.data < parent.data) {
    			// 将节点插入到parent的左边
    			parent.lchild = insertNode;
    		} else {
    			parent.rchild = insertNode;
    		}
    	}
    	
    	// 用node2来替代node1
    	private void replace(BinaryTreeNode node1, BinaryTreeNode node2){
    		// node2可以为null,这样就将删除叶子结点的情况包含在里面了
    		if (node1.parent == null) {
    			root = node2;
    		} else if (node1 == node1.parent.lchild) {
    			node1.parent.lchild = node2;
    		} else {
    			node1.parent.rchild = node2;
    		}
    		if (node2 != null)
    			node2.parent = node1.parent;
    	}
    	
    	// 删除某个结点
    	public void delete(BinaryTreeNode node) {
    		if (node == null) return;
    		if (node.lchild == null) { // 左子树为空,只需要移植右子树
    			replace(node, node.rchild);
    		} else if (node.rchild == null) {
    			replace(node, node.lchild);
    		} else { 
    			BinaryTreeNode predecessorOfnode = predecessor(node);
    			replace(node, node.lchild);
    			
    			predecessorOfnode.rchild = node.rchild;
    			node.rchild.parent = predecessorOfnode;
    		}
    	}	
    	
    	// 删除某个结点
    	@Deprecated
    	public void deleted(BinaryTreeNode node) {
    		if (node == null) return;
    		if (node.lchild == null) { // 左子树为空,只需要移植右子树
    			// -------1.用该结点的右子树代替它-------
    			// node = node.rchild;
    			if (node.parent == null) {
    				root = node.rchild;
    			} else if (node == node.parent.lchild) {
    				node.parent.lchild = node.rchild;
    			} else {
    				node.parent.rchild = node.rchild;
    			}
    			if (node.rchild != null)
    				node.rchild.parent = node.parent;
    		} else if (node.rchild == null) {
    			// -------2.用该结点的左子树代替它-------
    			// node = node.lchild;
    			if (node.parent == null) {
    				root = node.lchild;
    			} else if (node == node.parent.lchild) {
    				node.parent.lchild = node.lchild;
    			} else {
    				node.parent.rchild = node.lchild;
    			}
    			if (node.lchild != null)
    				node.lchild.parent = node.parent;
    		} else {
    			BinaryTreeNode predecessorOfnode = predecessor(node);
    			// -------3.用该结点的右子树代替它-------
    			if (node.parent == null) {
    				root = node.lchild;
    			} else if (node.parent.lchild == node) {
    				node.parent.lchild = node.lchild;
    			} else if (node.parent.rchild == node) {
    				node.parent.rchild = node.lchild;
    			}
    			node.lchild.parent = node.parent;
    			node.rchild.parent = predecessorOfnode;
    
    			// -------4.将右子树赋值给它的前驱-------
    			predecessorOfnode.rchild = node.rchild;
    			node.rchild.parent = predecessorOfnode;
    		}
    	}
    
    	// 返回某个节点node的前驱
    	public BinaryTreeNode predecessor(BinaryTreeNode node) {
    		if (node.lchild != null)
    			return maxNode(node.lchild);
    		BinaryTreeNode parent = node.parent;
    		while (parent != null && parent.lchild == node) {
    			node = parent;
    			parent = node.parent;
    		}
    		return parent;
    	}
    
    	// 返回某个节点node的后继
    	public BinaryTreeNode successor(BinaryTreeNode node) {
    		if (node.rchild != null)
    			return minNode(node.rchild);
    		BinaryTreeNode parent = node.parent;
    		while (parent != null && parent.rchild == node) {
    			node = parent;
    			parent = node.parent;
    		}
    		return parent;
    	}
    }


    测试

    public static void main(String[] args) {
    		BinarySearchTree bst = new BinarySearchTree();
    		int[] array = {1, 9, 2, 7, 4, 5, 3, 6, 8};
    		bst.createTree(array);
    		System.out.println("中序递归遍历");
    		bst.inOrder(bst.root);
    		System.out.println("");
    		System.out.println("层序遍历");
    		bst.levelOrderH();
    		bst.delete(bst.search(bst.root, 4));
    		System.out.println("将4删除后在中序递归遍历");
    		bst.inOrder(bst.root);
    	}

    输出

    中序递归遍历
    1  2  3  4  5  6  7  8  9  
    层序遍历
    1  
    9  
    2  
    7  
    4  8  
    3  5  
    6  
    
    将4删除后在中序递归遍历
    1  2  3  5  6  7  8  9  


    上面这棵树的形态大致如下


    可以清楚地看到,虽然这棵树严格满足BST的要求,但是这棵树是比较深的,而上述操作的时间复杂度直接与树的深度有关,也就是说如果创建的树高度太高,就会直接影响到BST上的操作的性能,因为上述操作的时间复杂度均为O(h),不要想当然地认为O(h)就是O(lgn)。像上面的这棵树,我们说他是不平衡的,平衡看起来,直观感受是做右子树高度差不多。平衡的二叉排序树即BBST,它的平均深度才为O(lgn),就能将上述操作的时间复杂度控制在O(lgn)以内。


  • 相关阅读:
    vba的单元格引用的总结
    为IE窗口添加菜单实例
    给SQLServer2000升级遇到的问题
    图片上传问题(含网页图片预览)
    javascript小技巧【待续】
    成功部署JSP网站的经验总结
    VBA实例
    为JDK增加新的jar包
    理解绝对定位和相对定位布局
    资源收集
  • 原文地址:https://www.cnblogs.com/qhyuan1992/p/5385280.html
Copyright © 2011-2022 走看看