二叉树学习笔记之二叉查找树(BSTree)

二叉查找树即搜索二叉树,或者二叉排序树(BSTree),学习回顾一下有关的知识。

>>关于二叉查找树

二叉查找树(Binary Search Tree)是指一棵空树或者具有下列性质的二叉树:
1. 若任意节点的左子树不空,则左子树上所有节点的值均小于它的根节点的值;
2. 若任意节点的右子树不空,则右子树上所有节点的值均大于它的根节点的值;
3. 任意节点的左、右子树也分别为二叉查找树。
4. 没有键值相等的节点,这个特征很重要,可以帮助理解二叉排序树的很多操作。
二叉查找树具有很高的灵活性,对其优化可以生成平衡二叉树,红黑树等高效的查找和插入数据结构。

>>基本性质

(1)二叉查找树是一个递归的数据结构,对二叉查找树进行中序遍历,可以得到一个递增的有序序列。
(2)二叉查找树上基本操作的执行时间和树的高度成正比。

对一棵n个节点的完全二叉树来说,树的高度为lgn,这些操作的最坏情况运行时间为O(lg n),而如果是线性链表结构,这些操作的最坏运行时间是O(n)。
一棵随机构造的二叉查找树的期望高度为O(lg n),但实际中并不能总是保证二叉查找树是随机构造的,
有些二叉查找树的变形能保证各种基本操作的最坏情况性能,比如红黑树的高度为O(lg n),而B树对维护随机访问的二级存储器上的数据库特别有效。

>>前驱和后继节点

一个节点的后继是该节点的后一个,即比该节点键值稍大的节点。
给定一个二叉查找树中的节点,找出在中序遍历顺序下某个节点的前驱和后继。
如果树中所有关键字都不相同,则某一节点x的前驱就是小于key[x]的所有关键字中最大的那个节点,后继即是大于key[x]中的所有关键字中最小的那个节点。根据二叉查找树的结构和性质,不用对关键字做任何比较,就可以找到某个节点的前驱和后继。

>>查找、插入与删除

(1)查找
利用二叉查找树左小右大的性质,可以很容易实现查找任意值和最大/小值。

在二叉查找树中查找一个给定的关键字k的过程与二分查找很类似

首先是关键字k与树根的关键字进行比较,如果k比根的关键字大,则在根的右子树中查找,否则在根的左子树中查找,重复此过程,直到找到与遇到空节点为止。

在二叉查找树中查找x的过程如下:
1.若二叉树是空树,则查找失败。
2.若x等于根节点的数据,则查找成功,否则。
3.若x小于根节点的数据,则递归查找其左子树,否则。
4.递归查找其右子树。

(2)插入
二叉树查找树b插入操作x的过程如下:
1.若b是空树,则直接将插入的节点作为根节点插入。
2.x等于b的根节点的数据的值,则直接返回,否则。
3.若x小于b的根节点的数据的值,则将x要插入的节点的位置改变为b的左子树,否则。
4.将x要出入的节点的位置改变为b的右子树。

(3)删除


假设从二叉查找树中删除给定的结点z,分三种情况讨论:
1.节点z为叶子节点,没有孩子节点,那么直接删除z,修改父节点的指针即可。
2.节点z只有一个子节点或者子树,将节点z删除,根据二叉查找树的性质,将z的父节点与子节点关联就可以了。
3.节点Z有两个子节点,删除Z该怎样将Z的父结点与这两个孩子结点关联呢?
在删去节点Z之后,为保持其它元素之间的相对位置不变,可按中序遍历保持有序进行调整。
这种情况下可以用Z的后继节点来替代Z。
实现方法就是将后继从二叉树中删除,将后继的数据覆盖到Z中。

>>代码实现

主要参考《数据结构与算法分析—Java语言实现》:

public class BinarySearchTree <T extends Comparable<? super T>>{

	//节点数据结构 静态内部类
	static class BinaryNode<T>{
		T data;
		BinaryNode<T> left;
		BinaryNode<T> right;
		public BinaryNode(){
			data=null;
		}
		public BinaryNode(T data) {
			this(data,null,null);
		}
		public BinaryNode(T data,BinaryNode<T> left,BinaryNode<T> right){
			this.data=data;
			this.left=left;
			this.right=right;
		}
	}

	//私有的头结点
	private BinaryNode<T> root;

	//构造一棵空二叉树
	public BinarySearchTree(){
		root=null;
	}
	//二叉树判空
	public boolean isEmpty(){
		return root==null;
	}
	//清空二叉树
	public void clear(){
		root=null;
	}

	//检查某个元素是否存在
	public boolean contains(T t){
		return contains(t,root);
	}

	/**
	 * 从某个节点开始查找某个元素是否存在
	 * 在二叉查找树中查找x的过程如下:
	 * 1、若二叉树是空树,则查找失败。
	 * 2、若x等于根结点的数据,则查找成功,否则。
	 * 3、若x小于根结点的数据,则递归查找其左子树,否则。
	 * 4、递归查找其右子树。
	 */
	public boolean contains(T t,BinaryNode<T> node){
		if(node==null){
			return false;
		}
		/**
		 * 这就是为什么使用Comparable的泛型
		 * compareTo的对象也必须是实现了Comparable接口的泛型,
		 * 所以参数必须是BinaryNode<T> node格式
		 */
		int result=t.compareTo(node.data);
		if(result>0){//去右子树查找
			return contains(t,node.right);
		}else if(result<0){//去左子树查找
			return contains(t,node.left);
		}else{
			return false;
		}
	}

	//插入元素
	public void insert(T t){
		root=insert(t,root);
	}

	/**
	 * 将节点插入到以某个节点为头的二叉树中
	 * 这个插入其实也是一个递归的过程
	 * 递归最深层的返回结果一个包含要插入的节点子树的头节点
	 */
	public BinaryNode insert(T t,BinaryNode<T> node){
		//如果是空树,直接构造一棵新的二叉树
		if(node==null){
			return new BinaryNode<T>(t);
		}

		int result=t.compareTo(node.data);

		if(result<0){
			node.left=insert(t,node.left);
		}else if(result>0){
			node.right=insert(t,node.right);
		}else{
			;//即要插入的元素和头节点值相等,直接返回即可
		}
		return node;
	}

	/**
	 * 删除元素
	 * 返回调整后的二叉树头结点
	 */
	public BinaryNode delete(T t){
		return delete(t,root);

	}

	/**
	 * 在以某个节点为头结点的树结构中删除元素
	 * 首先需要找到该关键字所在的节点p,然后具体的删除过程可以分为几种情况:
	 * p没有子女,直接删除p
	 * p有一个子女,直接删除p
	 * p有两个子女,删除p的后继q(q至多只有一个子女)
	 * 确定了要删除的节点q之后,就要修正q的父亲和子女的链接关系,
	 * 然后把q的值替换掉原先p的值,最后把q删除掉
	 */
	public BinaryNode delete(T t,BinaryNode<T> node){
		if(node==null){//节点为空还要啥自行车
			return node;
		}
		/**
		 * 首先要找到这个节点,所以还是得比较
		 */
		int result=t.compareTo(node.data);

		/**
		 * 去左半部分找这个节点,
		 * 找到节点result==0,这个递归就停止
		 */
		if(result<0){
			node.left=delete(t,node.left);
		}else if(result>0){//去右半部分找这个节点
			node.right=delete(t,node.right);
		}
		/**
		 * 如果这个节点的左右孩子都不为空,那么找到当前节点的后继节点,
		 *
		 */
		if(node.left!=null && node.right!=null){
			/**
			 * node节点的右子树部分的最小节点,实际上就是它的后继节点
			 * 得到后继节点的值
			 */
			node.data = findMin(node.right).data;
			/**
			 * 这个过程并不是删除后继节点,是一步一步的把所有的节点都替换上来
			 */
	        node.right = delete(node.data,node.right);
		}else{
			/**
			 * 如果二叉搜索树中一个节点是完全节点,
			 * 那么它的前驱和后继节点一定在以它为头结点的子树中,应该是这样的
			 * 来到了只有一个头节点和一个子节点的情况
			 */
			node = (node.left!=null)?node.left:node.right;
		}
		//此处的node,是经过调整后的传入的root节点
		return node;

	}

	/**
	 * 返回二叉树中的最小值节点
	 * 此时无比想念大根堆和小根堆
	 */
	public BinaryNode<T> findMin(BinaryNode node){
		if(node==null)
			return null;
		/**
		 * 如果node不为空,就递归的去左边找
		 * 最小值节点肯定是左孩子为空的节点
		 */
		if(node.left!=null)
			node=findMin(node.left);
		return node;
	}

}

  

参考整理自《算法导论》、《数据结构与算法分析—Java语言实现》 二叉查找树维基百科

时间: 2024-10-04 04:27:21

二叉树学习笔记之二叉查找树(BSTree)的相关文章

二叉树学习笔记。

1. 层序构建和先序遍历: 1 public class Tree { 2 public Tree left; 3 public Tree right; 4 public int val; 5 6 public Tree() { 7 8 } 9 10 public Tree(String n) { 11 this.val = Integer.parseInt(n); 12 } 13 14 public Tree createTreeByLevel(String[] var) { 15 if (v

二叉树学习笔记之树的旋转

树旋转(Tree rotation)是二叉树中的一种子树调整操作,每一次旋转并不影响对该二叉树进行中序遍历的结果.树旋转通常应用于需要调整树的局部平衡性的场合. >>左旋和右旋 树的旋转有两种基本的操作,即左旋(逆时针方向旋转)和右旋(顺时针方向旋转). 树旋转包括两个不同的方式,分别是左旋转(以P为转轴)和右旋转(以Q为转轴).两种旋转呈镜像,而且互为逆操作. 下图示意了两种树旋转过程中, 子树的初态和终态 +---+ +---+ | Q | | P | +---+ +---+ / \ ri

二叉树学习笔记1

数组.向量.链表都是一种顺序容器,它们提供了按位置访问数据的手段.而很多情况下,我们需要按数据的值来访问元素,而不是它们的位置来访问元素.比如有这样一个数组int num[3]={1,2,3},我们可以非常快速的访问数组中下标为2的数据,也就是说我们知道这个数据的位置,就可以快速访问.有时候我们是不知道元素的位置,但是却知道它的值是多少.假设我们有一个变量,存放在num这个数组中,我们知道它的值为2,却不知道它下标是多少,也就是说不知道它的位置.这个时候再去数组中访问这个元素就比较费劲,就得遍历

二叉树学习笔记-实现

上一篇文章中,算是初步了解了二叉树是一种怎样的数据结构,也算是有了一个初步的印象.接下来用自己的代码去实现一个二叉搜索树(以下全叫二叉树)类,对外提供常用的接口,比如insert.erase.size.find等等.就像盖房一样,如果说二叉树是一座建筑,那么其中的节点就是一块块砖.要实现二叉树这个类,就必须先实现节点类,假设我们起名为treeNode.在STL标准库中,像一般的数据结构都是模板类,在这里为了方便起见,假设二叉树这个类中保存的数据全是int型. 这个节点类中,需要包含如下的一些成员

数据结构学习系列之二叉查找树

二叉查找树(BST)是二叉树的一个重要的应用,它在二叉树的基础上加上了这样的一个性质:对于树中的每一个节点来说,如果有左儿子的话,它的左儿子的值一定小于它本身的值,如果有右儿子的话,它的右儿子的值一定大于它本身的值. 二叉查找树的操作一般有插入.删除和查找,这几个操作的平均时间复杂度都为O(logn),插入和查找操作很简单,删除操作会复杂一点,除此之外,因为二叉树的中序遍历是一个有序序列,我就额外加上了一个中序遍历操作. 二叉树有三种遍历方式:前序遍历(pre-order), 中序遍历(in-o

java学习笔记13--比较器(Comparable、Comparator)

java学习笔记13--比较器(Comparable.Comparator) 分类: JAVA 2013-05-20 23:20 3296人阅读 评论(0) 收藏 举报 Comparable接口的作用 之前Arrays类中存在sort()方法,此方法可以直接对对象数组进行排序. Comparable接口 可以直接使用java.util.Arrays类进行数组的排序操作,但对象所在的类必须实现Comparable接口,用于指定排序接口. Comparable接口的定义如下: public  int

集合类学习笔记

一.概念 集合是存储对象的一种方式.集合中都是存放着地址,方便引用.JDK 1.2版本的时候就有了 二.集合和数组的区别 集合是可变长度,数组是固定长度. 数组可以存储基本数据类型,集合只能存储对象,集合可以存储不同类型的对象. Collection 1.List:元素是有序的,可以重复,有索引 2.Set:元素是无序的,不可以重复,使用hash值排列 三.CURD boolean add(E e); boolean addAll(Collection<? extends E> c); voi

[学习笔记]数据结构与算法

1.排序简单排序:?冒泡排序:将n个数从上往下排列,从第0个数开始依次对前n个.前n-1个.前n-2个数进行比较,保持小数在前大数在后,不符合就交换.在这个过程中,最后一个数始终是最大数.?选择排序:对所有n个.后n-1个.后n-2个依次比较,用一个变量存最小数,一趟比较完成之后,将最小数与所比较数据的第一个数进行交换.在这个过程中,第一个数始终是最小数.?插入排序:从第1个数开始向前扫描比较,小则插入.对于未排序数据,在已排序序列中向前扫描,并找到相应的位置插入.在这个过程中,整个序列局部有序

面向对象先导学习笔记

面向对象先导学习笔记 经过了Python.C语言和数据结构的学习,感觉对于Java这门新学习的语言上手速度明显快了许多.在学习Java的过程中,个人觉得Java的语法规则在很大程度上与C类似,但是在设计程序和具体实现的过程中,更偏向于Python的思路,尤其是对于方法的调用和自带数据结构的使用方面. 数据类型的Java实现 Java自带了大量的数据类型,在完成作业的过程中,个人尝试通过手写二叉树完成问题,但是与Java自带的数据结构相比,无论是在稳定性和运行速度方面都有所欠缺.但是通过自己的摸索