Binary Search Tree(二叉搜索树、二叉查找树、二叉排序树)

搜索树数据结构支持许多动态几何操作,包括SEARCH、MININUM、MAXINUM、PREDECESSOR、SUCCESSOR、INSERT和DELETE等。因此,我们可以使用一个搜索树作为字典或者优先队列。

二叉搜索树上的基本操作所花费的时间与所花费的时间与这棵树的高度成正比。对于有n个结点的一棵完全二叉树来说,这些操作的最坏运行时间为Θ(logn)。然而如果这棵树是一条n个结点构成的线性链表(最坏情况下),那么同样的操作就要花费Θ(n)的最坏运行时间。

二叉搜索树是以二叉树来组织的结构,它可以使用链表数据结构来表示,每个节点(BSTreeNode)就是一个对象,用于排序的数据属性称为key,每个节点除了key和卫星属性外,还包含left、right和p,分别指向节点的左孩子、右孩子和双亲。如果某个孩子结点和父结点不存在,相应的属性则为NIL。

class BSTreeNode{
	int key;
	Object value;//data
	BSTreeNode left;
	BSTreeNode right;
	BSTreeNode p;//parent
	public BSTreeNode(int key){this.key = key;}
}

《Binary Search Tree(二叉搜索树、二叉查找树、二叉排序树)》

二叉搜索树总是满足以下性质:设x是二叉搜索树中的一个结点。如果y是x的左子树中的一个结点,那么y.key <= x.key。如果y是x右子树中的一个结点,那么y.key >= x.key。

二叉搜索树性质允许我们通过中序遍历(inorder tree walk)算法按序输出树中的所有关键字。对于一棵有n个结点的子树调用InorderTreeWalk算法,需要Θ(n)的时间。

public static void InorderTreeWalk(BSTreeNode x){
	if(x != null){
		InorderTreeWalk(x.left);
		System.out.println(x.key);
		InorderTreeWalk(x.right);
	}
}

查询二叉树

查询二叉树——查找关键字k

为了查找二叉搜索树中给定的关键字的结点,需要输入树根的指针和关键字k,如果这个结点存在,TreeSearch返回指向这个关键字为k的指针;否则返回NULL。

TreeSearch从树根开始查找,并沿着这棵树中的一条简单路径向下进行,对于遇到的么个结点x,比较关键字k与x.key,如果两个关键字相等,查找就终止,如果k < x.key,则继续在x的左子树上查找,否则就在x的右子树上查找。总的来说肯定会在树根到叶节点间的一条简单路径上遇到或者没遇到目标结点,所以TreeSearch的运行时间为O(h),其中h代表这棵树的高度。

public static BSTreeNode TreeSearch(BSTreeNode x, int k){
	if(x == null || k == x.key){
		return x;
	}
	if(k < x.key)
		return TreeSearch(x.left, k);
	else return TreeSearch(x.right, k);
}

例如要搜索结点5,从树根6开始,关键字5比6小,向关键字6左子树查找;关键字变为4,关键字5比4大,向关键字4右子树查找;关键字变为5,与目标关键字相同,结束查找,返回当前结点指针。

当然,我们也可以采用循环展开递归,用迭代完成搜索。

public static BSTreeNode IterativeTreeSearch(BSTreeNode x, int k){
	while(x != null && k != x.key){
		if(k < x.key) x = x.left;
		else x = x.right;
	}
	return x;
}

查询二叉树——最大关键字元素和最小关键字元素

从树根开始,沿着left孩子指针,直到遇到一个NULL,就可以查找到以结点x为根的子树中最小的元素的指针。

public static BSTreeNode TreeMininum(BSTreeNode x){
	while(x != null && x.left != null)
		x = x.left;
	return x;
}

若x为NULL,则不存在最小关键字。若x没有左子树,那么由于x中的每个关键字都至少大于或等于x.key,则以x为根的子树中的最小关键字是x.key。若x有左子树,那么由于其右子树中没有关键字小于x.key,且在左子树中每个关键字都不大于x.key,则以x为跟的子树中的最小关键字一定在以x.left为根的子树中。

此外,TreeMaxinum与TreeMininum代码对称:

public static BSTreeNode TreeMaxinum(BSTreeNode x){
	while(x != null && x.right != null)
		x = x.right;
	return x;
}

这两个操作在高度为h的树上均能在O(h)的时间内完成。

查询二叉树——后继和前驱

给定一棵二叉搜索树中的一个结点,有时候需要按照中序遍历的次序查找它后继。如果所有的关键字互不相同,则一个结点x的后继是大于x.key的最小关键字的结点。

public static BSTreeNode TreeSuccessor(BSTreeNode x){
	if(x.right != null)
		return TreeMininum(x.right);
	BSTreeNode y = x.p;
	while(y != null && x == y.right){
		x = y;
		y = y.p;
	}
	return y;
}

TreeSuccessor分为两种情况:如果结点x的右子树非空,那么x的后继恰是x右子树中的最左结点;如果结点x的右子树为空且y是它的后继,那么y就是x的有左孩子的最底层祖先,而且这个y的左孩子也是x的一个祖先。如结点5的后继是6,因为6是5的祖先,并且6的左孩子4也是5的祖先。

在一棵高度为h的树上,TreeSuccessor的运行时间为O(h),因为该过程或者遵从一条简单路径沿树向上或者遵从简单路径沿树向下。

TreePredecessor与TreeSuccessor是对称的,其运行时间也为O(h)。

public static BSTreeNode TreePredecessor(BSTreeNode x){
	if(x.left != null)
		return TreeMaxinum(x.right);
	BSTreeNode y = x.p;
	while(y != null && x == y.left){
		x = y;
		y = y.p;
	}
	return y;
}

插入和删除

插入和删除操作会引起由二叉搜索树表示的动态集合的变化,一定要通过修改数据结构来反映这种变化,但是修改要保持二叉搜索树性质的成立。

插入

要将值v插入二叉搜索树中,可以创建新的结点z,其中z.key=v,z.left=NULL,z.right=NULL,通过调用过程TreeInsert,修改搜索树T和z的某些属性,把z插入树中相应的位置。

public void TreeInsert(BSTreeNode z){//this为BSTree
	BSTreeNode y = null, x = this.root;
	while(x != null){
		y = x;
		if(z.key < x.key) x= x.left;
		else x = x.right;
	}
	z.p = y;
	if(y == null) this.root = z;           //tree is empty
	else if(z.key < y.key)
		y.left = z;
	else y.right = z;
}

TreeInsert从树根开始,指针x记录一条向下的简单路径,并查找要替换的输入项z的叶子结点。该过程保持遍历指针y作为x的双亲。在循环时,根据z.key和x.key的比较结果决定向左移或向右移,直到x变为NULL,这个NULL的位置就是输入项z要放置的位置,此时根据y的指针可以获得该位置的双亲节点,从而把z放置到当前位置。

TreeInsert在高度为h的树上的运行时间为O(h)。

删除

从二叉搜索树T中删除一个结点z分为三种基本情况:

1.如果z没有孩子节点,那么就简单的将它删除,并修改它的父结点,用NULL作为孩子替换z;
2.如果z只有一个孩子,那么将这个孩子提升到树中z的位置上,并修改z的父结点,用z的孩子来替换z;
3.如果z有两个孩子,那么找z的后继y(一定在z的右子树中),并让y占据树中z的位置。Z的原来右子树部分变成y的新的右子树,并且z的左子树成为y的新的左子树。(为什么找z的后继:根据中序遍历,z的后继一定是比z大的最小数字,故用这个数字替换z后,z左子树的数字肯定比它小,右子树的数字肯定比它大,这样就保持了搜索树的特性)

对以上情况具体分析来讲,又可以分为以下四种具体的情况:

1.如果z没有左孩子,那么用其右孩子来替换z,即使这个右孩子是NULL也可以用来替换。当z的右孩子是NULL时,这种情况可以归为z没有孩子节点的情况。当z的右孩子不为NULL时,这种情况就是z仅有一个孩子结点的情况,这个孩子就是其右孩子;

《Binary Search Tree(二叉搜索树、二叉查找树、二叉排序树)》

2.如果z仅有一个孩子,且为左孩子,那么就用z的左孩子替代z;

《Binary Search Tree(二叉搜索树、二叉查找树、二叉排序树)》

3.否则,z既有左孩子又有右孩子。我们要查找z的后继y,这个后继位于z的右子树中并且是没有左孩子的结点。要把这个y移出原来的位置,并替代z。

4.如果y是z的右孩子,那么用y替换z,并仅留下y的右孩子。

《Binary Search Tree(二叉搜索树、二叉查找树、二叉排序树)》

5.否则,y位于z的右子树中但并不是z的右孩子,这种情况下,先用y的右孩子替换y,然后再用y替换z。这种情况中,y是z的右子树下的最小元素(调用TreeMininum获取),然后用y的右子树x替换y,并且用y作为z的右子树的根,这样整体上来看就将这种情况变为上面的四种情况了,紧接着用y替换z即可。

《Binary Search Tree(二叉搜索树、二叉查找树、二叉排序树)》

private void TransPlant(BSTreeNode u, BSTreeNode v){// v replace u
	if(u.p == null) this.root = v;
	else if(u == u.p.left) u.p.left = v;
	else u.p.right = v;
	
	if(v != null)
		v.p = u.p;
}
public void TreeDelete(BSTreeNode z){
	if(z.left == null)
		TransPlant(z, z.right);
	else if(z.right == null)
		TransPlant(z, z.left);
	else{
		BSTreeNode y = TreeMininum(z.right);
		if(y.p != z){//不是右孩子
			TransPlant(y, y.right);
			y.right = z.right;
			y.right.p = y;
		}
		TransPlant(z, y);//y是z的右孩子
		y.left = z.left;
		y.left.p = y;
	}
}

其中,TransPlant表示用一棵以v为根的子树来替换一棵以u为根的子树,替换后结点u的双亲就变为结点v的双亲,并且最后v成为u的双亲的相应孩子。

TreeDelete分为三种情况:z没有左孩子,用z.right这个子树替换z;z没有右孩子,用z.right这棵子树替换z;z有两个孩子的情况,用y表示z中序遍历时的后继(也就是比z小的最大数字),这种情况分为两种类型,1. y是z的右孩子,这种情况下y必定是没有左孩子,可以用y直接替换z、2. Y不是z的右孩子,这种情况下,y是z的右子树中最左下的子树,并且没有左孩子,此时用y的右孩子替换y,并且将z的右子树作为y的右孩子(保证y左孩子为NULL)此时情况就转化为这种情况下的1,此时就可以直接用y这棵子树替换z。

TreeDelete中除了调用TreeMininum之外,都只花费常数时间,因此,在一棵高度为h的树上,TreeDelete的运行时间为O(h)。

故,在一棵高度为h的二叉搜索树上,实现动态集合操作Insert和Delete的运行时间均为O(h)。

附完整代码:

class BSTreeNode{
	int key;
	Object value;//data
	BSTreeNode left;
	BSTreeNode right;
	BSTreeNode p;//parent
	public BSTreeNode(int key){this.key = key;}
}

public class BSTree {

	public static void InorderTreeWalk(BSTreeNode x){
		if(x != null){
			InorderTreeWalk(x.left);
			System.out.println(x.key);
			InorderTreeWalk(x.right);
		}
	}
	//查找
	public static BSTreeNode TreeSearch(BSTreeNode x, int k){
		if(x == null || k == x.key){
			return x;
		}
		if(k < x.key)
			return TreeSearch(x.left, k);
		else return TreeSearch(x.right, k);
	}
	public static BSTreeNode IterativeTreeSearch(BSTreeNode x, int k){
		while(x != null && k != x.key){
			if(k < x.key) x = x.left;
			else x = x.right;
		}
		return x;
	}
	public static BSTreeNode TreeMininum(BSTreeNode x){
		while(x != null && x.left != null)
			x = x.left;
		return x;
	}
	public static BSTreeNode TreeMaxinum(BSTreeNode x){
		while(x != null && x.right != null)
			x = x.right;
		return x;
	}
	//后继、前驱
	public static BSTreeNode TreeSuccessor(BSTreeNode x){
		if(x.right != null)
			return TreeMininum(x.right);
		BSTreeNode y = x.p;
		while(y != null && x == y.right){
			x = y;
			y = y.p;
		}
		return y;
	}
	public static BSTreeNode TreePredecessor(BSTreeNode x){
		if(x.left != null)
			return TreeMaxinum(x.right);
		BSTreeNode y = x.p;
		while(y != null && x == y.left){
			x = y;
			y = y.p;
		}
		return y;
	}
	
	private BSTreeNode root;
	
	//插入
	public void TreeInsert(BSTreeNode z){//this为BSTree
		BSTreeNode y = null, x = this.root;
		while(x != null){
			y = x;
			if(z.key < x.key) x= x.left;
			else x = x.right;
		}
		z.p = y;
		if(y == null) this.root = z;           //tree was empty
		else if(z.key < y.key)
			y.left = z;
		else y.right = z;
	}
	//删除
	private void TransPlant(BSTreeNode u, BSTreeNode v){// v replace u
		if(u.p == null) this.root = v;
		else if(u == u.p.left) u.p.left = v;
		else u.p.right = v;
		
		if(v != null)
			v.p = u.p;
	}
	public void TreeDelete(BSTreeNode z){
		if(z.left == null)
			TransPlant(z, z.right);
		else if(z.right == null)
			TransPlant(z, z.left);
		else{
			BSTreeNode y = TreeMininum(z.right);
			if(y.p != z){//不是右孩子
				TransPlant(y, y.right);
				y.right = z.right;
				y.right.p = y;
			}
			TransPlant(z, y);//y是z的右孩子
			y.left = z.left;
			y.left.p = y;
		}
	}
	public static void main(String[] args) {
		BSTree T = new BSTree();
		int a[] = {12, 5, 2, 9, 18, 15, 13, 17, 19};
		for(int i : a){
			BSTreeNode node = new BSTreeNode(i);
			T.TreeInsert(node);
		}
		InorderTreeWalk(T.root);
	}
}

    原文作者:二叉查找树
    原文地址: https://blog.csdn.net/teaandnoodle/article/details/52263731
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞