二叉搜索树

一、二叉搜索树的定义


      二叉搜索树又称二叉查找树,它或者是一棵空树,或者是一棵具有如下特性的非空二叉树:

(1)若它的左子树非空,则左子树上所有结点的关键字均小于根结点的关键字。

(2)若它的右子树非空,则右子树上所有接单的关键字均大于根结点的关键字(特殊地,若允许树中国存在着相同的关键字元素,则此条件应改为大于等于)。

(3)左、右子树本身又是一棵二叉搜索树。

        在一棵二叉搜索树中,当每个结点的元素类型为java语言中的基本数据类型及包装类时,则结点的关键字就是该结点的元素值;当每个结点所保存的元素类型为一般的类类型时,则结点的关键字为该结点所保存的元素值中的一个数据成员的值。例如,当元素的类型为int及对应的包装类Integer时,则结点的关键字就是该结点的值即整数;当元素的类型为学生类型Student时,则每个学生的学号就是相应元素的关键字。在下面的算法描述中,均以结点值的比较来代表其关键字的比较,因此结点中保存的元素值的实际类型必须实现系统提供的Comparable接口并定义有compareTo成员方法,以便进行对象大小的比较,即相应的关键字大小的比较。

       由二叉搜索树的定义可知,在一棵非空的二叉搜索树中,其结点的关键字是按照左子树、根、右子树的次序,从小到大有序排列的,所以对其进行中序遍历得到的节点值序列必然是一个有序序列。

       如下图7-1就是一棵二叉搜索树,树中每个分支结点的关键字都大于它左子树中所有结点的关键字,而小于它右子树中所有结点的关键字。对此树进行中序遍历得到的结点序列为:

                                                      

                                                         

        12,15,18,23,26,30,52,63,74

         可见此序列是一个有序序列。


二、二叉搜索树的抽象数据类型和链接存储类

        

    二叉搜索树的抽象数据类型除了与一般二叉树的抽象数据类型完全相同外,还需要有自己的特殊方法,如查找,更新,插入和删除元素等方法。根据二叉搜索树的特点,这些方法都比较容易实现。下面给出用Java语言描述的二叉搜索树的接口类BinarySearchTree,它继承一般二叉树的接口类BinaryTree。

public interface BinarySearchTree extends BinaryTree {

	//从二叉搜索树中查找值为obj的结点,若存在则返回结点值,否则返回空值null
	Object find(Object obj);
	//从二叉搜索树中更新值为obj的结点,若更新成功则返回原值否则返回空
	Object update(Object obj);
	//向二叉搜索树插入值为obj的结点,使得插入后仍是一棵二叉搜索树,若插入成功
	//则返回真,若碰到有相同结点,假定不需要插入,认为是插入失败而返回假
	boolean insert(Object obj);
	//从二叉搜索树中删除值为obj的结点,若成功则返回真,否则返回假
	boolean delete(Object obj);
	//按照关键字从小到大的次序遍历输出一棵二叉搜索树中的所有结点值
	void ascend();
}


1、查找

        根据二叉搜索树的定义,从二叉搜索树中查找其值等于给定值obj的结点时,比在一般二叉树上的查找要简单。其查找过程可叙述为:若二叉搜索树为空,则表明查找失败,应返回null;否则,若obj等于当前树根结点的值,则表明查找成功,应返回结点的完整值;若obj小于当前树根结点的值,则继续同左子树的根结点比较;若obj大于当前树根结点的值;则继续同右子树的根结点比较。通过从树根沿着左孩子或右孩子逐层向下比较,每比较一次,下移一层,当查找到取值为obj的结点,表明查找成功,应返回该结点的值;或者查找到空指针,表明查找失败,应返回空值null。显然这是一个循环查找的过程,其循环次数等于树的深度。

       对二叉搜索树进行查找的算法描述为:

//从二叉搜索树中查找值为obj的结点,若存在则返回结点值,否则返回空值null
	public Object find(Object obj) {
		if(root==null)                              //若为空树,查找失败,返回空值
		{
			return null;
		}
		BTreeNode st=root;                          //树根指针赋给st
		while(st!=null)
		{
			if(((Comparable)obj).compareTo(st.element)==0)
			{
				return st.element;                  //查找成功返回结点值
			}
			else if(((Comparable)obj).compareTo(st.element)<0)
			{
				st=st.left;                         //向左子树继续查找
			}
			else
			{
				st=st.right;                        //向右子树继续查找
			}
		}
		return null;                                //查找失败返回空值
	}

       在二叉搜索树上进行查找的过程中,给定值obj同树中结点比较的次数最少为一次(即树根几点就是待查的结点),最多为树的深度,所以平均查找次数要小于等于树的深度。若二叉搜索树是一棵理想平衡树或接近理想平衡树,则进行查找的时间复杂度为O(log2n),若退化为一棵单支树(这是最极端和最差的情况),则其时间复杂度为O(n),对于一般情况,其时间复杂度大致为O(log2n)。由此可知,在二叉搜索树上查找比在集合或线性表上进行顺序查找的时间复杂度O(n)要好得多,这正是利用二叉搜索树的优势所在。二叉搜索树查找算法的空间复杂度为O(1)。


2、更新

        二叉搜索树的更新算法与查找算法基本相同,仅有一点区别:即在更新算法中当查找到待更新的结点时,应将参数obj的值赋给该结点,并返回该结点的原值,而在查找算法中只将该结点的值返回。

       对二叉搜索树进行的更新算法描述为:

//从二叉搜索树中更新值为obj的结点,若更新成功则返回原值否则返回空
	public Object update(Object obj) {
		if(root==null)
		{
			return null;                  //空树,不存在值为obj的结点则更新失败,返回空值
		}
		BTreeNode st=root;                //树根指针赋给st
		while(st!=null)
		{
			if(((Comparable)obj).compareTo(st.element)==0)
			{
				//查找到给定结点时进行更新并返回结点的原值
				Object x=st.element;
				st.element=obj;
				return x;
			}
			else if(((Comparable)obj).compareTo(st.element)<0)
			{
				st=st.left;                 //向左子树继续查找待更新的结点
			}
			else
			{
				st=st.right;                //向有子树继续查找待更新的结点
			}
		}
		return null;                        //更新失败,返回空值
	}

3、插入

        在二叉搜索树上进行插入结点时,保证二叉搜索树的性质不变,即插入操作后二叉搜索树必须是按照左子树、根结点和右子树有序排列的。对于二叉搜索树进行插入的过程是:手下需要查找新结点的插入位置,然后再进行插入链接。查找插入位置从树根结点开始,若树根指针为空,则新结点就是树根结点;否则,若obj等于根结点,则表明具有obj值的结点已经存在,不允许进行插入,应返回false表示插入失败;若obj小于根结点,则沿着根在左指针在左子树上继续查找插入位置,若obj大于根结点,则沿着根的右指针在右子树上继续查找插入位置。依次逐层向下查找,当查找到一个结点(假定由pt指针所指向)的左指针或右指针为空时,则这个空的指针位置就是obj新结点的插入位置。

       在进行插入链接时,若原树为空,则将新结点指针(引用)赋给root,该新结点就成为整个二叉搜索树的树根结点,否则,将新结点赋给pt结点的左指针域或右指针域,作为该结点的左孩子或右孩子。插入链接完成后返回true表示插入成功。

       对二叉搜索树插入元素过程的具体算法描述为:

//向二叉搜索树插入值为obj的结点,使得插入后仍是一棵二叉搜索树,若插入成功
	//则返回真,若碰到有相同结点,假定不需要插入,认为是插入失败而返回假
	public boolean insert(Object obj) {
		BTreeNode st=root,pt=null;          //st初始指向树根结点,pt指向st的双亲结点
		//从树根开始逐层向下为新元素obj寻找插入位置
		while(st!=null)
		{
			pt=st;
			if(((Comparable)obj).compareTo(st.element)==0)
			{
				return false;              //有重复元素,元素插入失败,返回假
			}
			else if(((Comparable)obj).compareTo(st.element)<0)
			{
				st=st.left;                //从左子树中寻找插入位置
			}
			else
			{
				st=st.right;               //从右子树中寻找插入位置
			}
		}
		//将新结点插入到二叉搜索树中的确定位置上
		BTreeNode s=new BTreeNode(obj);    //建立值为obj,左,右指针为空的新结点
		if(pt==null)
		{
			root=s;                        //将新结点作为树根插入
		}
		else if(((Comparable)obj).compareTo(pt.element)<0)
		{
			pt.left=s;                     //将新结点作为pt的左孩子插入
		}
		else
		{
			pt.right=s;                    //将新结点作为pt的右孩子插入
		}
		return true;                       //返回真,表示插入成功
	}

        二叉搜索树的插入算法与查找和更新算法一样,都具有相同的时间复杂度和空间复杂度。

        利用二叉搜索树的插入算法可以很容易地从空树开始生成一棵具有n个结点的二叉搜索树。假定一棵二叉搜索树用bst表示,开始为空,待插入的n个元素由数组a提供,其中n等于a.length,则通过下面一条for循环语句就可以生成包含n个结点的二叉搜索树:

		for(int i=0;i<a.length;i++)
		{
			bst.insert(a[i]);
		}
        在一般情况下,生成一棵二叉搜索树的时间复杂度为O( n log2n)。

4、删除

        二叉搜索树的的删除比插入要复杂一些,因为被插入的结点都是被链接到树中的叶子结点上,因而不会破坏树的原有结构,也就是说,不会破坏树中原有结点之间的链接关系。从二叉搜索树上删除结点则不同,它可能删除叶子结点,也可能删除的是分支结点,当删除分支结点时,就破坏了原有结点之间的链接关系,需要重新修改指针,使得删除后认为一棵二叉搜索树。

下面结合图(a)所示的二叉搜索树,分三种情况说明删除结点的操作过程。

(1)删除叶子结点

       此种删除操作很简单,只要将其双亲结点链接到它的指针去掉(即置为空)即可。例如,删除图(a)树中叶子结点A时,把D结点的左指针域置空即可;删除叶子结点W时,把S结点的右指针域置空即可。


(2)删除单支结点

         这种删除操作也很简单,因为该结点只有左子树或右子树一支,也就是说,其后继左孩子或右孩子一个。删除该结点时,只要将唯一的一个后继指针链接到它所在的链接位置即可。例如,删除图7-3(a)树中单支结点D时,将G的左指针(即指向F结点的指针)赋给D结点的右指针域即可。删除单支结点M时,将M的右指针(即指向S结点的指针)赋给L结点的右指针域即可;删除这两个结点后,得到的二叉搜索树如图7-3(b)所示。

(3)删除双支结点

         这种删除比较复杂,因为待删除的结点又两个后继指针,需要妥善处理。删除这种结点的常用方法是:首先把它的中序前驱结点(即中序序列中处于它前面的一个结点,该结点时其左子树中最右下的一个结点)的值赋给该结点的值域,然后再删除它的中序前驱结点,因为它的中序前驱结点的右指针必为空,所以只要把中序前驱结点的左指针链接到中序前驱结点所在的链接位置即可。例如,删除图7-3(a)树中双支结点D时,则首先把它的中序前驱结点A的值赋给D结点的值域,然后把A结点在左指针(此时为空)链接到D结点的左指针域,删除D结点后得到的二叉搜索树如图7-3(c)所示,又如,若从图7-3(a)树中删除树根结点L,因L是双支结点,所以首先把它的中序前驱结点G的值赋给L结点的值域,然后把G结点的左指针(此时指向F结点)链接到D结点的右指针域,删除L结点后得到的二叉搜索树如图7-3(d)所示。

从二叉搜索树中删除结点的算法描述如下:

//从二叉搜索树中删除值为obj的结点,若成功则返回真,否则返回假
	public boolean delete(Object obj) {
		if(root==null)
		{
			return false;                  //若树为空则无法删除,返回假
		}
		BTreeNode st=root,pt=null;         //st初始指向树根结点,pt指向st的双亲
		//从树根开始逐层向下查找待删除的值为obj的结点
		while(st!=null)
		{
			if(((Comparable)obj).compareTo(st.element)==0)
			{
				break;                      //找到对应的结点则退出循环
			}
			else if(((Comparable)obj).compareTo(st.element)<0)
			{
				pt=st;st=st.left;           //向左子树继续查找
			}
			else
			{
				pt=st;st=st.right;          //向右子树继续查找
			}
		}
		if(st==null)
		{
			return false;                   //若没有找到待删除的结点,则返回假
		}
		//分三种不同情况删除已查找到的st结点
		if(st.left==null&&st.right==null)
		{
			//st结点为叶子结时的处理过程
			if(st==root)
			{
				root=null;                  //对树根结点进行特殊处理
			}
			else if(pt.left==st) 
			{
				pt.left=null;
			}
			else
			{
				pt.right=null;
			}
		}
		else if(st.left==null||st.right==null)
		{
			//st结点为单分支时的处理过程
			if(st==root)                    //对st为树根结点的情况进行特殊处理
			{
				if(st.left==null)
				{
					root=st.right;
				}
				else
				{
					root=st.left;
				}
			}
			else if(pt.left==st&&st.left==null)
			{
				pt.left=st.right;
			}
			else if(pt.left==st&&st.right==null)
			{
				pt.left=st.left;
			}
			else if(pt.right==st&&st.left==null)
			{
				pt.right=st.right;
			}
			else if(pt.right==st&&st.right==null)
			{
				pt.right=st.left;
			}
		}
		else if(st.left!=null&&st.right!=null)
		{
			//st结点为双分支结点时的处理过程
			BTreeNode s1=st,s2=st.left;          //s1初始指向st结点,s2指向左孩子
			while(s2.right!=null)                //沿左孩子的右子树查找其中序前驱结点
			{
				s1=s2;
				s2=s2.right;
			}
			st.element=st.element;               //把中序前驱结点s2的值赋给st结点
			//删除右子树为空的s2结点,使它的左子树链接到它所在的链接位置
			if(s1==st)                           //对st的中序前驱结点是st的左孩子的情况进行处理
			{
				st.left=s2.left;
			}
			else
			{
				s1.right=s2.left;
			}
		}
		return true;                             //删除成功返回真
	}

        二叉搜索树的查找、更新、插入和删除元素的运算都具有相同的时间复杂度,都与具体二叉搜索树的深度成正比,时间复杂度的平均情况大致为O(log2n),最差情况为O(n);他们的空间复杂度均为O(1)。

        二叉搜索树的链接存储类应继承一般二叉树的链接存储类LinkBinaryTree和实现二叉搜索树的接口类BinarySearchTree,具体定义如下:

//二叉搜索树的链接存储类的定义
public class LinkBinarySearchTree extends LinkBinaryTree implements BinarySearchTree {

	//无参构造函数的定义
	public LinkBinarySearchTree() {
		super();                                    //初始设置二叉搜索树为空树
	}

	//带参构造函数的定义
	public LinkBinarySearchTree(BTreeNode st) {
		super(st);                                  //把以知二叉搜索树的树根引用赋给root
	}

	//从二叉搜索树中查找值为obj的结点,若存在则返回结点值,否则返回空值null
	public Object find(Object obj) {
		if(root==null)                              //若为空树,查找失败,返回空值
		{
			return null;
		}
		BTreeNode st=root;                          //树根指针赋给st
		while(st!=null)
		{
			if(((Comparable)obj).compareTo(st.element)==0)
			{
				return st.element;                  //查找成功返回结点值
			}
			else if(((Comparable)obj).compareTo(st.element)<0)
			{
				st=st.left;                         //向左子树继续查找
			}
			else
			{
				st=st.right;                        //向右子树继续查找
			}
		}
		return null;                                //查找失败返回空值
	}

	//从二叉搜索树中更新值为obj的结点,若更新成功则返回原值否则返回空
	public Object update(Object obj) {
		if(root==null)
		{
			return null;                  //空树,不存在值为obj的结点则更新失败,返回空值
		}
		BTreeNode st=root;                //树根指针赋给st
		while(st!=null)
		{
			if(((Comparable)obj).compareTo(st.element)==0)
			{
				//查找到给定结点时进行更新并返回结点的原值
				Object x=st.element;
				st.element=obj;
				return x;
			}
			else if(((Comparable)obj).compareTo(st.element)<0)
			{
				st=st.left;                 //向左子树继续查找待更新的结点
			}
			else
			{
				st=st.right;                //向有子树继续查找待更新的结点
			}
		}
		return null;                        //更新失败,返回空值
	}

	//向二叉搜索树插入值为obj的结点,使得插入后仍是一棵二叉搜索树,若插入成功
	//则返回真,若碰到有相同结点,假定不需要插入,认为是插入失败而返回假
	public boolean insert(Object obj) {
		BTreeNode st=root,pt=null;          //st初始指向树根结点,pt指向st的双亲结点
		//从树根开始逐层向下为新元素obj寻找插入位置
		while(st!=null)
		{
			pt=st;
			if(((Comparable)obj).compareTo(st.element)==0)
			{
				return false;              //有重复元素,元素插入失败,返回假
			}
			else if(((Comparable)obj).compareTo(st.element)<0)
			{
				st=st.left;                //从左子树中寻找插入位置
			}
			else
			{
				st=st.right;               //从右子树中寻找插入位置
			}
		}
		//将新结点插入到二叉搜索树中的确定位置上
		BTreeNode s=new BTreeNode(obj);    //建立值为obj,左,右指针为空的新结点
		if(pt==null)
		{
			root=s;                        //将新结点作为树根插入
		}
		else if(((Comparable)obj).compareTo(pt.element)<0)
		{
			pt.left=s;                     //将新结点作为pt的左孩子插入
		}
		else
		{
			pt.right=s;                    //将新结点作为pt的右孩子插入
		}
		return true;                       //返回真,表示插入成功
	}

	//从二叉搜索树中删除值为obj的结点,若成功则返回真,否则返回假
	public boolean delete(Object obj) {
		if(root==null)
		{
			return false;                  //若树为空则无法删除,返回假
		}
		BTreeNode st=root,pt=null;         //st初始指向树根结点,pt指向st的双亲
		//从树根开始逐层向下查找待删除的值为obj的结点
		while(st!=null)
		{
			if(((Comparable)obj).compareTo(st.element)==0)
			{
				break;                      //找到对应的结点则退出循环
			}
			else if(((Comparable)obj).compareTo(st.element)<0)
			{
				pt=st;st=st.left;           //向左子树继续查找
			}
			else
			{
				pt=st;st=st.right;          //向右子树继续查找
			}
		}
		if(st==null)
		{
			return false;                   //若没有找到待删除的结点,则返回假
		}
		//分三种不同情况删除已查找到的st结点
		if(st.left==null&&st.right==null)
		{
			//st结点为叶子结时的处理过程
			if(st==root)
			{
				root=null;                  //对树根结点进行特殊处理
			}
			else if(pt.left==st) 
			{
				pt.left=null;
			}
			else
			{
				pt.right=null;
			}
		}
		else if(st.left==null||st.right==null)
		{
			//st结点为单分支时的处理过程
			if(st==root)                    //对st为树根结点的情况进行特殊处理
			{
				if(st.left==null)
				{
					root=st.right;
				}
				else
				{
					root=st.left;
				}
			}
			else if(pt.left==st&&st.left==null)
			{
				pt.left=st.right;
			}
			else if(pt.left==st&&st.right==null)
			{
				pt.left=st.left;
			}
			else if(pt.right==st&&st.left==null)
			{
				pt.right=st.right;
			}
			else if(pt.right==st&&st.right==null)
			{
				pt.right=st.left;
			}
		}
		else if(st.left!=null&&st.right!=null)
		{
			//st结点为双分支结点时的处理过程
			BTreeNode s1=st,s2=st.left;          //s1初始指向st结点,s2指向左孩子
			while(s2.right!=null)                //沿左孩子的右子树查找其中序前驱结点
			{
				s1=s2;
				s2=s2.right;
			}
			st.element=st.element;               //把中序前驱结点s2的值赋给st结点
			//删除右子树为空的s2结点,使它的左子树链接到它所在的链接位置
			if(s1==st)                           //对st的中序前驱结点是st的左孩子的情况进行处理
			{
				st.left=s2.left;
			}
			else
			{
				s1.right=s2.left;
			}
		}
		return true;                             //删除成功返回真
	}

	//按照关键字从小到大的次序遍历输出一棵二叉搜索树中的所有结点值
	public void ascend() {
		traverseBtree("inOrder");           //通过调用中序遍历方法实现
	}

}




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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值