算法笔记-二叉树2

二叉查找树:树中的任意一个节点,它的左子树节点的值都小于该节点的值,它的右子树节点的值都大于该节点的值。
图例如下:
在这里插入图片描述

二叉查找树-查找

获取根节点,如果根节点为空,如果根节点的值等于该值,则返回根节点。如果该值小于根节点,则在根节点的左子树中递归查找。如果该值大于根节点,则在根节点的右子树中递归查找。

代码如下:

//树节点
class TreeNode{
	int data;
	TreeNode leftNode;
	TreeNode rightNode;
	
	public TreeNode(int data){
		this.data = data;
	}
	//二叉查找树查询
	public static TreeNode selectTreeNode(TreeNode tree, int val){
		TreeNode node = tree;
		if(node.data == val){
			return node;
		}
		while(node != null && node.data != val){
			if(val > node.data){
				node = node.rightNode;
			}else if(val < node.data){
				node = node.leftNode;
			}else{
				return node;
			}			
		}
		return null;
	}
	
}
二叉查找树-插入

插入的逻辑类似于查找。首先,与根节点的值对比,如果大于根节点,则对比根节点的右子节点,然后依次继续上述的对比逻辑,直到某一节点没有可以对比的子节点的时候,创建一个新节点,赋值,作为上一节点的子节点。如果大小于根节点,则对比根节点的左子节点,然后依次继续上述的对比逻辑,直到某一节点没有可以对比的子节点的时候,创建一个新节点,赋值,作为上一节点的子节点。

//树节点
class TreeNode{
	int data;
	TreeNode leftNode;
	TreeNode rightNode;
	
	public TreeNode(int data){
		this.data = data;
	}
	
	//二叉查找树插入节点
	public static TreeNode insertTreeNode(TreeNode tree, int val){
		TreeNode node = tree;
		while(node != null){
			if(val > node.data){
				if(node.rightNode == null){
					node.rightNode = new TreeNode(val);
				}else{
					node = node.rightNode;
				}
			}
			if(val < node.data){
				if(node.leftNode == null){
					node.leftNode = new TreeNode(val);
				}else{
					node = node.leftNode;
				}
			}
		}
		return null;
	}

补充:删除插入节点的方法只针对于插入无重复值的节点。

二叉查找树-删除

删除节点时分以下三种情况:

1:如果被删除节点分别左右子节点的时候,需要查找到该节点的右子书的最小节点,将其值付给该节点,并删除这个最小节点。

2:被删除节点有一个子节点的时候,该节点的父节点直接指向子节点。

3:被删除节点没有节点的时候,该节点的父节点指向 null 就可以了。

代码如下:

//树节点
class TreeNode{
	int data;
	TreeNode leftNode;
	TreeNode rightNode;
	
	public TreeNode(int data){
		this.data = data;
	}
	//删除节点
	public static TreeNode deleteTreeNode(TreeNode tree,int val){
		TreeNode p = tree;//要删除的节点
		TreeNode pp = null;//p 节点的父节点
		while(p != null && p.data != val){
			pp = p;
			if(val > p.data){
				p = p.rightNode;
			}
			if(val < p.data){
				p = p.leftNode;
			}
		}
		
		if(p == null){
			return null;//没有找到要删除的节点
		}
		
		//删除有左右子节点的节点
		if(p.leftNode != null && p.rightNode != null){
			TreeNode minp = p;//要删除的节点
			TreeNode minpp = p;//minp 节点的父节点
			while(minp.leftNode != null){
				minpp = minp;
				minp = minp.leftNode;
			}
			/**
			 * 此处将查找到的右子树最小节点的值赋值给 p 节点的数据域。
			 * 然后 p 节点指向 minp 节点,pp 节点指向 minpp 节点
			 * 原先删除有左右子节点的节点问题,变成了删除没有子节点的节点
			 */
			p.data = minp.data;
			p = minp;
			pp = minpp;
		}
		
		/**
		 * 删除只有一个子节点和没有子节点的节点
		 * (删除有左右子节点的节点那部分删除逻辑也放在这里执行了)
		 */
		TreeNode child;
		if(p.leftNode != null){
			child = p.leftNode;
		}else if(p.rightNode != null){
			child = p.rightNode;
		}else{
			child = null;
		}
		
		if(pp == null){//删除的是根节点
			tree = child;
		}else if(pp.leftNode == p){
			pp.leftNode = child;
		}else{
			pp.rightNode = child;
		}
		return p;
	}	
}
二叉查找树的其他操作

查找最大值的节点:从根节点开始递归查找右子节点,直到找到叶子节点,该节点就是值最大的节点。

查找最小值的节点:从根节点开始递归查找左子节点,直到找到叶子节点,该节点就是值最小的节点。

中序遍历输出二叉查找树的节点,正好是按照值从小到大的顺序输出。

支持重复数据的二叉查找树

方法一:二叉查找树的节点维护的是一个数组,该数组支持动态扩容,我们把键值相同的数据放在该节点的数组中。或者,二叉查找树的节点维护的是一个链表,我们把键值相同的数据插入到该链表的尾部。

方法二:插入值相同的节点的时候,将该值放入该节点的右子树中,也就是说相同的值, 我们当做大于该值来处理。

针对方法二,查找节点的时候,遇到相同值的节点的时候,继续在该节点的右子树查找,直到查找到叶子节点位置。删除节点的时候,查找到所有要删除的节点,然后利用删除删除节点的方法依次删除。还有一种简便的删除方法,对要删除的节点做删除标记,这样不改变树的结构,就完成了删除,只是比较浪费空间。

在实际应用中,二叉查找树的节点并非只是存储一个值,而是存储一个对象。利用对象的某个属性值,作为键值构建二叉查找树,其他数字段叫做卫星数据。

二叉查找树的时间复杂度分析

极端情况下,除根节点以外的其他节点都是父节点的左子节点或者是右子节点,则二叉查找树就退化为链表了,查找的时间复杂度为 O(n)。

理想情况下,二叉查找树是一个完全二叉树(满二叉树),那么查找到某个节点的最坏时间复杂度就是该二叉查找树的高度了,所以时间复杂度就是 O(high)。

推理过程省略了,我不会推理,直接总结结论:一个有着 n 个节点的完全二叉查找树,它的高度小于等于 log2|n,所以时间复杂度为 O(logn)。

总结

二叉查找树和散列表的对比:

1:散列表是无序的,如果想要有序输出,必须对数据排序。而二叉查找树只要中序遍历输出,就是一个有序的输出。

2:散列表扩容耗时多,散列冲突严重的时候,性能不稳定。二叉查找树的性能不稳定,但是平衡二叉查找树性能稳定。

3:散列表查找数据的时间复杂度是常量级别的,但是因为有散列冲突,所以常量级别的时间复杂度不一定比 logn 的值小。同时,哈希函数的运算也需要耗时,未必就比二叉查找树的查找效率高。

4:散列表的构造比较复杂,既要考虑散列函数的高效,又要考虑散列冲突的解决方法,对于扩容缩容也需要合适的设计。而平衡二叉查找树就没有这么些限制,只需要考虑性能问题就行。

本文创作灵感来源于 极客时间 王争老师的《数据结构与算法之美》课程,通过课后反思以及借鉴各位学友的发言总结,现整理出自己的知识架构,以便日后温故知新,查漏补缺。

初入算法学习,必是步履蹒跚,一路磕磕绊绊跌跌撞撞。看不懂别慌,也别忙着总结,先读五遍文章先,无他,唯手熟尔~
与诸君共勉

关注本人公众号,第一时间获取最新文章发布,每日更新一篇技术文章。

在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值