当前位置: 首页 > 编程日记 > 正文

关于二叉树的几个必须掌握的实现

The best way to end your fear is to face it yourself. 
结束恐惧的最佳办法就是自己面对。

本分分享了二叉搜索树的几种实现,由简入繁。说句题外话,马上又是金三银四的季节了,无论跳不跳槽,增加自己的知识储备总是没错的。从代码里读原理,读思想。

所有源码均已上传至github:链接

日志

2019年3月28日 AVL树

准备

public class Node {/*** data*/public int data;/*** 左节点*/public Node left;/*** 右节点*/public Node right;public Node(int data) {this.data = data;}}复制代码

实现一个支持插入、删除、查找操作的二叉查找树,并且找二叉树的最大值和最小值操作

插入操作

传入一个data值,当tree为null,初始化tree,否则遍历tree,比较data与node.data的值,大于的话插入右节点,否则插入左节点。

    private void insert(int data) {if (null == tree) {tree = new Node(data);return;}Node node = tree;while (null != node) {if (data > node.data) {if (null == node.right) {node.right = new Node(data);return;}node = node.right;} else {if (null == node.left) {node.left = new Node(data);return;}node = node.left;}}}复制代码

查找

查找的实现也比较简单,遍历tree的左右节点,直到找到data == node.data

    private Node find(int data) {Node node = tree;while (null != node) {if (data > node.data) {node = node.right;} else if (data < node.data) {node = node.left;} else {return node;}}return null;}复制代码

找二叉树的最大值

找二叉树的最大值最小值比较简单,按照树的结构遍历即可

    private Node findMaxNode() {if (null == tree) return null;Node node = tree;while (null != node.right) {node = node.right;}return node;}复制代码

找二叉树的最小值

    private Node findMinNode() {if (null == tree) return null;Node node = tree;while (null != node.left) {node = node.left;}return node;}复制代码

删除操作

删除是这里面最复杂的操作了,这里详细讲一下。

针对删除的节点的位置不同需要考虑以下三种情况:

  1. 如果要删除的节点没有子节点,只需要直接将父节点中,指向要删除节点的指针置为 null。
  2. 如果要删除的节点只有一个子节点(左子节点或者右子节点),只需要更新父节点中,指向要删除节点的指针,让它指向要删除节点的子节点就可以了。
  3. 如果要删除的节点有两个子节点,这就比较复杂了。需要找到这个节点的右子树中的最小节点,把它替换到要删除的节点上。然后再删除掉这个最小节点,因为最小节点肯定没有左子节点(如果有左子结点,那就不是最小节点了)。

    private void delete(int data) {Node node = tree;//node的父节点Node pNode = null;while (null != node && node.data != data) {pNode = node;if (data > node.data) {node = node.right;} else {node = node.left;}}if (null == node) return;if (null != node.left && null != node.right) {Node p = node.right;//p的父节点Node pp = node;while (null != p.left) {pp = p.left;p = p.left;}//删除操作node.data = p.data;node = p;pNode = pp;}Node child;if (null != node.left) {child = node.left;} else if (null != node.right) {child = node.right;} else {child = null;}if (null == pNode) {tree = child;} else if (pNode.left == null) {pNode.left = child;} else {pNode.right = child;}}复制代码

实现查找二叉查找树中某个节点的后继、前驱节点

ps:因为这里测试的节点都是根节点,因为比较简单,不需要遍历该节点的父节点。

后续如果需要,欢迎留言,我补充一下。

查找前驱节点

    private Node findPreNode(Node node) {if (null == node) return null;if (null != node.left){Node left = node.left;while (null != left.right){left = left.right;}return left;}return null;}复制代码

查找后继节点

    private Node findNextNode(Node node) {if (null == node) return null;if (null != node.right) {Node right = node.right;while (null != right.left) {right = right.left;}return right;}return null;}复制代码

实现二叉查找树前、中、后序以及层级遍历

前序遍历

    public void preOrderTraversal(Node tree) {if (null == tree) return;System.out.print(tree.data + " ");preOrderTraversal(tree.left);preOrderTraversal(tree.right);}复制代码

中序遍历

    public void inOrderTraversal(Node tree) {if (null == tree) return;inOrderTraversal(tree.left);System.out.print(tree.data + " ");inOrderTraversal(tree.right);}复制代码

后序遍历

    public void postOrderTraversal(Node tree) {if (null == tree) return;postOrderTraversal(tree.left);postOrderTraversal(tree.right);System.out.print(tree.data + " ");}复制代码

层级遍历

层级遍历的思想是维护一个队里,每一次遍历tree,将tree的节点打印,然后将tree的left和right节点存入队列里(每一次打印时从队列里取即可)。

    public void levelTraversal(Node tree) {if (null == tree) return;LinkedList<Node> linkedList = new LinkedList<>();Node node;linkedList.offer(tree);while (!linkedList.isEmpty()) {node = linkedList.poll();System.out.print(node.data + " ");if (null != node.left) {linkedList.offer(node.left);}if (null != node.right) {linkedList.offer(node.right);}}}复制代码

AVL树

定义

AVL树是一种平衡二叉树。貌似就windows对进程地址空间的管理用到了AVL树。

  1. 左右子树的高度差小于等于 1。
  2. 其每一个子树均为平衡二叉树。

核心

AVL树核心在于左旋和右旋上,当出现不平衡的情况时,它会自动调整。

声明一个AVL树的Node类

在普通二叉树的基础上新增了几个属性

  1. balance 是平衡标识,它的区间是[-1,1]
  2. height 是该节点的高度,自上而下计算,从0开始

    public class Node {/*** 数据*/int data;/*** 高度*/int height;/*** 平衡标识*/int balance;/*** 左子树*/Node left;/*** 右子树*/Node right;/*** 根*/Node parent;/*** 有参构造方法** @param data   数据* @param parent 父节点*/Node(int data, Node parent) {this.data = data;this.parent = parent;}}复制代码

插入

插入方法和常规插入的区别是需要将当前节点作为根节点插入,并且执行一下自平衡方法。

 private void insert(int data) {if (null == root) {root = new Node(data, null);} else {Node node = root;Node parent;while (node.data != data) {parent = node;boolean goleft = node.data > data;node = goleft ? node.left : node.right;if (null == node) {if (goleft) {parent.left = new Node(data, parent);} else {parent.right = new Node(data, parent);}rebalance(parent);break;}}}}复制代码

删除

这里是根据节点删除。和常规的删除相比,也是多了一步自平衡。

    private void delete(Node node) {if (null == node.left && null == node.right) {if (null == node.parent) {root = null;} else {Node parent = node.parent;if (parent.left == node) {parent.left = null;} else {parent.right = null;}rebalance(parent);}}if (null != node.left) {Node child = node.left;while (null != child.right) {child = child.right;}node.data = child.data;delete(child);} else {Node child = node.right;if (null != child) {while (null != child.left) {child = child.left;}node.data = child.data;delete(child);}}}复制代码

自平衡

有这么四种情况。


这里是否需要自平衡,是根据balance来判断的,当balance为-2的时候,(如果该节点的左子树的左子树的高度小于右子树高度,左旋,)右旋;当balance为2的时候,(如果该节点的右子树的右子树的高度小于左子树高度,右旋,)左旋;最后判断根节点是否为null,是则递归,否则赋值,停止递归;平衡完毕。

    private void rebalance(Node node) {setBalance(node);if (node.balance == -2) {if (getHeight(node.left.left) < getHeight(node.left.right)) {node.left = rotateLeft(node.left);}node = rotateRight(node);} else if (node.balance == 2) {if (getHeight(node.right.right) < getHeight(node.right.left)) {node.right = rotateRight(node.right);}node = rotateLeft(node);}if (null != node.parent) {rebalance(node.parent);} else {root = node;}}复制代码

左右旋

左旋和右旋的原理是一样的,只要了解一种即可。

    private Node rotateLeft(Node node) {Node right = node.right;right.parent = node.parent;node.right = right.left;if (null != node.right)node.right.parent = node;right.left = node;node.parent = right;if (null != right.parent) {if (right.parent.right == node) {right.parent.right = right;} else {right.parent.left = right;}}setBalance(node, right);return right;}复制代码

    private Node rotateRight(Node node) {Node left = node.left;left.parent = node.parent;node.left = left.right;if (null != node.left)node.left.parent = node;left.right = node;node.parent = left;if (null != left.parent) {if (left.parent.right == node) {left.parent.right = left;} else {left.parent.left = left;}}setBalance(node, left);return left;}复制代码

设置平衡点

首先是重置高度,取左右子树高度的最大值加一作为该节点的高度。

其次是设置平衡点,用来判断该节点是否平衡

    private void setBalance(Node... nodes) {for (Node node : nodes) {if (null != node) {//重置高度node.height = Math.max(getHeight(node.left), getHeight(node.right)) + 1;node.balance = getHeight(node.right) - getHeight(node.left);}}}复制代码

获取树的高度

    private int getHeight(Node node) {if (null != node)return node.height;return -1;}复制代码

测试代码

这里打印用的中序遍历(中序遍历可以从小到大打印出来)

    private void print(Node node) {if (null != node) {print(node.left);System.out.println(node.data + " " + node.balance);print(node.right);}}public static void main(String[] args) {AVLTree avlTree = new AVLTree();for (int i = 1; i < 10; i++) {avlTree.insert(i);}avlTree.print();}复制代码

测试结果

4

2              6

1          3    5           8

7           9


data - balance


end


您的点赞和关注是对我最大的支持,谢谢!



相关文章:

python数据结构与算法:队列与双端队列

双端队列&#xff1a; #################队列#################### #coding:utf-8 """ Deque() 创建一个空的双端队列 add_front(item) 从队头加入一个item元素 add_rear(item) 从队尾加入一个item元素 remove_front() 从队头删除一个item元素 remove_rear() 从…

view5.3登录桌面提示当前可用桌面资源不足

问题描述&#xff1a;用户反馈有个桌面经常提示当前可用桌面资源不足&#xff0c;开始的时候反复重启还可以使用&#xff0c;今天发现彻底无法登录了。解决方法&#xff1a;首先登录到view administrator管理平台查看该桌面发现状态是可用&#xff0c;说明桌面正常&#xff0c;…

【HDU】4706 Children's Day(模拟)

http://acm.hdu.edu.cn/showproblem.php?pid4706 该题没有输入&#xff0c;直接输出不同形状大小的N&#xff0c;在输出不同形状N的时候是要用到26个字母&#xff0c;并且是循环输出 #include <iostream>using namespace std;char map[60][60]; char a[] "abcdef…

详解原生AJAX请求demo(兼容IE5/6)

function createXHR(){ // 检测原生XHR对象是否存在if ( window.XMLHpptRequest ){// 如果存在就返回新实例return new XMLHpptRequest();} else { // 如果不存在就检测ActiveX对象// 兼容IE5/6return new ActiveXObject(Microsoft.XMLHttp);} }// 在所有的浏览器中创建XHR对象…

【POJ】3268 Silver Cow Party (将有向图的边反转)

问题链接&#xff1a;http://poj.org/problem?id3268 【问题描述】 One cow from each of N farms (1 ≤ N ≤ 1000) conveniently numbered 1…N is going to attend the big cow party to be held at farm #X (1 ≤ X ≤ N). A total of M (1 ≤ M ≤ 100,000) unidirectio…

项目管理深入理解08--成本管理

成本管理一章非常的重要&#xff0c;尤其是对于程序员来说&#xff0c;这方面非常的薄弱&#xff0c;但这部分知识无论是在项目管理中还是日常生活中都灰常重要&#xff0c;不然很难成为一个财务自由的程序员。此外&#xff0c;由于财务方面知识点比较多&#xff0c;特增加经济…

python数据结构与算法:双向链表

双向链表&#xff1a; ###################### P4.13-P4. 双向链表 ########################### # import singlelinkListclass Node(object):def __init__(self,item):self.elem itemself.next Noneself.prev None# class DoublelinkList(singlelinkList): #继承 class …

如何开发一个区块链应用程序

区块链是一项巧妙的发明&#xff0c;有望使数字世界更加安全和分散。通过允许数字信息的分发而不是复制&#xff0c;区块链技术创建了一种新型互联网。最初是为数字货币比特币而设计的&#xff0c;现在科技界正在寻找该技术的其他潜在用途。在不久的将来&#xff0c;我们将看到…

python数据结构与算法:栈

栈&#xff1a; Stack() 创建一个新的空栈 push(item) 添加一个新的元素item到栈顶 pop() 弹出栈顶元素 peek() 返回栈顶元素 is_empty() 判断栈是否为空 size() 返回栈的元素个数 Stack() 创建一个新的空栈 push(item) 添加一个新的元素item到栈顶 pop() 弹出栈顶元素 peek(…

【PAT (Basic Level) 】1014 福尔摩斯的约会 (20 分)

大侦探福尔摩斯接到一张奇怪的字条&#xff1a; 我们约会吧&#xff01; 3485djDkxh4hhGE 2984akDfkkkkggEdsb s&hgsfdk d&Hyscvnm大侦探很快就明白了&#xff0c;字条上奇怪的乱码实际上就是约会的时间星期四 14:04&#xff0c;因为前面两字符串中第 1 对相同的…

菜鸟物流云是如何帮助快递合作伙伴解决双11巨大业务负荷的?

物流云双11 双11前&#xff0c;菜鸟物流云共接入12家合作伙伴&#xff0c;全部参加双11大促活动&#xff0c;作为物流云的首次双11&#xff0c;尤其是经过了快递公司的大考经验&#xff0c;事实证明项目是靠谱的。 双11前已经整体上云的快递合作伙伴2家&#xff0c;韵达和天天&…

安装H3C的各种问题

HCL安装完成后&#xff0c;启动HCL失败&#xff1b;提示&#xff1a;“当前系统用户名中包含非ASCII字符”问题&#xff1f;HCL只能安装装在英文路径下&#xff0c;如果用户名为中文或者安装路径有中文目录&#xff0c;就会出现此问题&#xff0c;请确保系统用户名和安装路径中…

前景背景分割——ostu算法的原理及实现 OpenCV (八)

OpenCV 【八】——前景背景分割——ostu算法的原理及实现 实验结果代码实现实现原理参考资料实验结果 代码实现 #include<opencv2/opencv.hpp> #include<iostream> using namespace std; using namespace cv; //计算图像灰度直方图 Mat calcgrayhist(const Mat&am…

【PAT (Basic Level) 】1015 德才论 (25 分)

宋代史学家司马光在《资治通鉴》中有一段著名的“德才论”&#xff1a;“是故才德全尽谓之圣人&#xff0c;才德兼亡谓之愚人&#xff0c;德胜才谓之君子&#xff0c;才胜德谓之小人。凡取人之术&#xff0c;苟不得圣人&#xff0c;君子而与之&#xff0c;与其得小人&#xff0…

浏览器启动外部软件

常可以看见使用浏览器代码启动本地应用的软件.例如qq、迅雷、等等.那么他们是怎么做到的呢? 它的奥秘:Register protocol 前言我们经常看到 tencent://..thunder://这两种开头的网址&#xff0c;往往觉得很奇怪&#xff0c;很想弄懂其中的原理&#xff0c;是如何实现的&#x…

Luogu P1082 同余方程(NOIP 2012) 题解报告

题目传送门 【题目大意】 求关于x的同余方程 ax≡1(mod b)的最小整数解。 【思路分析】 由同余方程的有关知识可得&#xff0c;ax≡1(mod b)可以化为axby1&#xff0c;此方程有解当且仅当gcd(a,b)1&#xff0c;于是就可以用欧几里得算法求出一组特解x0&#xff0c;y0。 那么x0就…

MATLAB【二】————图像做减法,批量文本处理,子图显示

clear; clc; close all;name_string ["1.5ms\100\" ];length strlength(name_string); [m,n] size(length);%%----------------------------- for num1:mstr name_string(num,1); figure(color, [1, 1, 1], position, [0, 0, 1800,800]); % 为区分边界&a…

与数据有关的问题

&#xfeff;&#xfeff;◆ 背景说明 在为用户排查问题&#xff0c;解决问题时&#xff0c;有一种情况是不容易引起大家注意的&#xff0c;那就是用户的数据&#xff1b;比如&#xff0c;数据中有某些特殊字符&#xff0c;引起展现不了或展现不正常&#xff1b;现在&#xff…

【PAT (Basic Level) 】1024 科学计数法 (20 分)

科学计数法是科学家用来表示很大或很小的数字的一种方便的方法&#xff0c;其满足正则表达式 [][1-9].[0-9]E[][0-9]&#xff0c;即数字的整数部分只有 1 位&#xff0c;小数部分至少有 1 位&#xff0c;该数字及其指数部分的正负号即使对正数也必定明确给出。 现以科学计数法…

jsp 实栗 jsp + jdbc 登录

jsp 实栗 jsp jdbc 实现登录 实现思路 一个表单页&#xff0c;输入用户登录和密码&#xff0c;然后信息提交到jsp页面进行验证&#xff0c;如果可以服务器跳转到登录成功页&#xff0c;失败&#xff0c;跳转到错误页 跳转的时候窗口的URL地址会发生变化 代码如下 编写登录代码…

OpenCV 【十】——Gamma校正 ——图像灰度变化

Gamma校正&#xff08;C、OpenCV实现&#xff09; 1.作用&#xff1a; Gamma校正是对输入图像灰度值进行的非线性操作&#xff0c;使输出图像灰度值与输入图像灰度值呈指数关系&#xff1a; 伽玛校正由以下幂律表达式定义&#xff1a; 2.函数原型 void calcHist( const Mat*…

Linux磁盘阵列技术详解(二)--raid 1创建

我在Linux磁盘阵列技术详解&#xff08;一&#xff09;里已经详细介绍了几种RAID磁盘阵列方式&#xff0c;原理以及创建raid 0 的详细步骤。那么这篇文档就着重讲解如何创建raid 1的技术&#xff1a;步骤如下&#xff1a;① 分区同样我们还是以一块硬盘的不同分区为例&#xff…

【PAT (Basic Level) 】1025 反转链表 (25 分)

给定一个常数 K 以及一个单链表 L&#xff0c;请编写程序将 L 中每 K 个结点反转。例如&#xff1a;给定 L 为 1→2→3→4→5→6&#xff0c;K 为 3&#xff0c;则输出应该为 3→2→1→6→5→4&#xff1b;如果 K 为 4&#xff0c;则输出应该为 4→3→2→1→5→6&#xff0c;即…

C#关于窗体的传值

关于窗体之间的传值我在《编程技巧与维护》杂志上写过总结文章&#xff0c;比较久远了。 开始的时候&#xff0c;用下面的方法传递&#xff0c;程序运行正常。 Form1 f1 this.Owner as Form1; //Form1 f1 (Form1)this.Owner;&#xff08;这样写也可以&#xff09; …

MATLAB【四】 ————批量适配图片信息与excel/txt等文档信息,批量移动拷贝图片,批量存图片中点和方框

1、批量读取图片&#xff0c;批量读取文件 2、适配文件与excel、txt等文档信息 3、获取显示图片ROI、Point、rect、更改像素值 4、批量移动拷贝图片&#xff0c;批量显示 5、保存显示图片或者图片中的点和方框。 clear; clc; close all;%% crop the im into 256*256 num 0…

mysql日志文件相关的配置【2】

1、二进制日志是什么&#xff1f; mysql 的二进制日志用于记录数据库上做的变更、 2、二进制日志什么时间写到磁盘 1、总的来说二进制日志会在释放锁之前就写入磁盘、也就是说在commit完成之前&#xff1b;client还没发送commit这个时候mysql并不把binlog写入磁盘、 别一方面my…

【PAT (Basic Level) 】1028 人口普查 (20 分)

某城镇进行人口普查&#xff0c;得到了全体居民的生日。现请你写个程序&#xff0c;找出镇上最年长和最年轻的人。这里确保每个输入的日期都是合法的&#xff0c;但不一定是合理的——假设已知镇上没有超过 200 岁的老人&#xff0c;而今天是 2014 年 9 月 6 日&#xff0c;所以…

SW6206超级华为快充5V5A,全协议OPPO闪充、自带电量计量、LED 灯/数码管显示

深圳市展嵘电子有限公司有需要的上帝可联系小陈&#xff1a;136-6225-3950 : 3412-1522-98SW6206 是一款高集成度的多协议双向快充移动电源专用多合一芯片&#xff0c;支持AABCL 口任意口快充。其集成了5A高效率开关充电&#xff0c;20W高效率同步升压输出&#xff0c;PPS/PD/Q…

bash脚本【一】——批量处理文件

Bash脚本2.0 #!/bin/bashoutput_root_dir"0723weixin" data_root_dir"D:/data/"$output_root_dir config_dir"config"# speckle_name"SPEACKLEIMAGE.bmp" # ir_name"IRIMAGE.bmp" # rgb_name"RGBIMAGE.jpg" # co…

【PAT (Basic Level) 】1030 完美数列 (25 分)

给定一个正整数数列&#xff0c;和正整数 p&#xff0c;设这个数列中的最大值是 M&#xff0c;最小值是 m&#xff0c;如果 M≤mp&#xff0c;则称这个数列是完美数列。 现在给定参数 p 和一些正整数&#xff0c;请你从中选择尽可能多的数构成一个完美数列。 【输入格式】&…