「周末福报」如何实现一棵二叉树?
人最大的对手,往往不是别人,而是自己的懒惰。
昨天,生拉硬拽的更新一期「学习笔记」;
让我们重新回到课堂学习“树和二叉树”的基础知识;
今天,从零开始写一个 Java 版本的二叉树。
正片开始
徒手开撕二叉树之前,了解玩基础知识之后,还需要了解下它的存储结构、抽象数据类型。
存储结构
二叉树通常采用链式存储结构,每个结点至少要有两条链分别链接左、右孩子结点,才能表达二叉树的层次关系。
图 A 二叉树
每个结点元素都包含三个元素:数据 data、左孩子 left,右孩子 right。
抽象数据类型 Abstract Data Type
需要实现的二叉树功能。
MyBinaryTree {
boolean isEmpty(); // 是否为空二叉树
int size(); // 二叉树的结点个数
int height(); // 二叉树的高度
void preorder(); // 输出先根次序遍历序列
void inorder(); // 输出中根次序遍历序列
void postorder(); // 输出后根次序遍历序列
void levelorder(); // 输出层次遍历序列
MyBinaryNode insert(T x); // 插入元素x作为根结点并返回
MyBinaryNode insert(MyBinaryNode p, T x, boolean leftChild); // 插入x作为p的左/右孩子
void remove(MyBinaryNode parent, boolean leftChild); // 删除 parent 结点的左/右孩子
void clear(); // 删除二叉树的所有结点
MyBinaryNode search(T key); // 查找并返回关键字为 Key 的结点
boolean contains(T key); // 判断是否包含关键字为 key 的元素
int level(T key); // 返回关键字为 key 结点所在的层次
}
以上的功能基本可以涵盖二叉树的完整使用。
为了跟可能存在的自带类做区分,结点类命名为 MyBinaryNode,二叉树类命名为 MyBinaryTree。
二叉树实现
MyBinaryNode 结点类
为了支持各种数据都能使用,采用泛型 T 定义类。
public class MyBinaryNode<T> {
public T data; // 数据
public MyBinaryNode<T> left, right; // 左孩子、右孩子结点
// 构造结点
public MyBinaryNode(T data, MyBinaryNode<T> left, MyBinaryNode<T> right) {
this.data = data;
this.left = left;
this.right = right;
}
public MyBinaryNode(T data) {
this.data = data;
}
public String toString() {
return data.toString();
}
}
MyBinaryTree 二叉树类
为了支持各种数据都能使用,采用泛型 T 定义类。
public MyBinaryNode<T> root;
// 默认构造器
public MyBinaryTree() {
root = null;
}
/**
* 是否为空二叉树
*
* @return
*/
public boolean isEmpty() {
return root == null;
}
插入结点
在二叉树中插入结点会存在两种插入情况。
当创建X结点作为 parent 的左孩子,parent 的原左孩子作为X结点的左孩子。
当创建Y结点作为 parent 的右孩子,parent 的原右孩子作为Y结点的右孩子。
/**
* 插入元素x作为根结点并返回
*
* @param x
* @return
*/
public MyBinaryNode<T> insert(T x) {
return this.root = new MyBinaryNode<T>(x, this.root, null);
}
/**
* 插入x作为p的左/右孩子
*
* @param p
* @param x
* @param leftChild
* @return
*/
public MyBinaryNode<T> insert(MyBinaryNode<T> parent, T x, boolean leftChild) {
if (x == null)
return null;
if (leftChild)
return parent.left = new MyBinaryNode<T>(x, parent.left, null);
else
return parent.right = new MyBinaryNode<T>(x, null, parent.right);
}
删除结点
在二叉树中删除一个结点,不仅要修改其父母结点的 left 或 right,还要约定如何调整整个子树结构的规则。(涉及后续左、右子树旋转的情况,这里不做展开)
这里直接删除结点,以及它的左、右子树。
/**
* 删除 parent 结点的左/右孩子
*
* @param parent
* @param leftChild
*/
public void remove(MyBinaryNode<T> parent, boolean leftChild) {
if (parent == null)
throw new NullPointerException();
if (leftChild)
parent.left = null;
else
parent.right = null;
}
/**
* 删除二叉树的所有结点
*/
public void clear() {
this.root = null;
}
先根次序遍历(递归版)
/**
* 输出先根次序遍历序列
*/
public void preorder() {
preorder(this.root);
System.out.println();
}
private void preorder(MyBinaryNode<T> p) {
if (p != null) {
System.out.print(p.data.toString() + " ");
// 按先根次序遍历 p 的左子树
preorder(p.left);
// 按先根次序遍历 p 的右子树
preorder(p.right);
}
}
中根次序遍历(递归版)
/**
* 输出中根次序遍历序列
*/
public void inorder() {
inorder(this.root);
System.out.println();
}
private void inorder(MyBinaryNode<T> p) {
if (p != null) {
inorder(p.left);
System.out.print(p.data.toString() + " ");
inorder(p.right);
}
}
后根次序遍历(递归版)
/**
* 输出后根次序遍历序列
*/
public void postorder() {
postorder(this.root);
System.out.println();
}
private void postorder(MyBinaryNode<T> p) {
if (p != null) {
postorder(p.left);
postorder(p.right);
System.out.print(p.data.toString() + " ");
}
}
层次遍历
在二叉树中的层次遍历,指的是兄弟优先。
也就是两个兄弟结点的访问次序是先左后右,连续访问。左兄弟的孩子一定在右兄弟的所有孩子之前访问,左兄弟的后代结点一定在右兄弟的同层后代结点之前访问。
(图 A 中)从 B 到 C,从 C 到 D,从 E 到 F 都没有链结构,也就是说二叉树本身的链结构是无法满足需求的,必须设立辅助的数据结构,指出下一个访问结点是谁。即 “先进先出” 特点的队列。
/**
* 输出层次遍历序列
*/
public void levelorder() {
System.out.println("层次遍历:");
ConcurrentLinkedQueue<MyBinaryNode<T>> que = new ConcurrentLinkedQueue<MyBinaryNode<T>>();
MyBinaryNode<T> p = this.root;
while(p != null) {
System.out.print(p.data + " ");
if (p.left != null)
que.add(p.left);
if(p.right != null)
que.add(p.right);
p = que.poll();
}
System.out.println();
}
结点个数
/**
* 二叉树的结点个数
*
* @return
*/
public int size() {
return size(this.root);
}
private int size(MyBinaryNode<T> p) {
if (p == null)
return 0;
// 后根遍历
int l = size(p.left);
int r = size(p.right);
return l + r + 1;
}
二叉树的高度
/**
* 二叉树的高度
*
* @return
*/
public int height() {
return height(this.root);
}
private int height(MyBinaryNode<T> p) {
if (p == null)
return 0;
int l = height(p.left);
int r = height(p.right);
if (l == 0 && r == 0)
return 1;
if (l > r)
return ++l;
else
return ++r;
}
查询关键字 Key
/**
* 查找并返回关键字为 Key 的结点
* @param key
* @return
*/
public MyBinaryNode<T> search(T key) {
return search(this.root, key);
}
private MyBinaryNode<T> search(MyBinaryNode<T> p, T key) {
if (p == null)
return null;
if (p.data.equals(key))
return p;
MyBinaryNode<T> l = search(p.left, key);
if (l != null)
return l;
MyBinaryNode<T> r = search(p.right, key);
if (r != null)
return r;
return null;
}
/**
* 判断是否包含关键字为 key 的元素
*
* @param key
* @return
*/
public boolean contains(T key) {
return search(this.root, key) != null;
}
/**
* 返回关键字为 key 结点所在的层次
*
* @param key
* @return
*/
public int level(T key) {
return level(this.root, key);
}
private int level(MyBinaryNode<T> p, T key) {
if (p == null)
return 0;
if (p.data.equals(key)) return 1;
int l = level(p.left, key);
int r = level(p.right, key);
if (l == 0 && r == 0)
return 0;
if (l > r)
return ++l;
else
return ++r;
}
测试类
public class MyBinaryTreeTest {
@SuppressWarnings("unused")
public static void initTree(MyBinaryTree<String> tree) {
tree.clear();
MyBinaryNode<String> root = tree.insert("A");
MyBinaryNode<String> treeB = tree.insert(root, "B", true);
MyBinaryNode<String> treeC = tree.insert(root, "C", false);
MyBinaryNode<String> treeD = tree.insert(treeB, "D", true);
MyBinaryNode<String> treeE = tree.insert(treeC, "E", true);
MyBinaryNode<String> treeF = tree.insert(treeC, "F", false);
MyBinaryNode<String> treeG = tree.insert(treeD, "G", false);
MyBinaryNode<String> treeH = tree.insert(treeF, "H", true);
}
public static void main(String[] args) {
MyBinaryTree<String> tree = new MyBinaryTree<>();
initTree(tree);
System.out.print("先根次序遍历:");
tree.preorder();
System.out.print("中根次序遍历:");
tree.inorder();
System.out.print("后根次序遍历:");
tree.postorder();
System.out.println("结点个数:" + tree.size());
System.out.println("树的高度:" + tree.height());
tree.levelorder();
System.out.println(tree.search("G"));
System.out.println(tree.contains("G"));
System.out.println(tree.contains("X"));
}
}
测试结果
先根次序遍历:A B D G C E F H
中根次序遍历:D G B A E C H F
后根次序遍历:G D B E H F C A
结点个数:8
树的高度:4
层次遍历:
A B C D E F G H
G
true
false
小结
早上起来,满脑子都是树、二叉、左子树、右子树。。。
今天,咬咬牙从零开始完整的捋了捋二叉树的实现。
其实,二叉树还有很多高深的用法。常考题目 - 红黑树、无损压缩 - Huffman 树等等。
当然这些也在我的知识盲区列表之中,后续会好好学习、努力整理出来的。
PS. 对递归理解有困难的同学,可以看历史文章《递归算法》
这个周末,又一次成功“强迫”自己学习。
感谢各位小伙伴的阅读,这里是一个技术人的学习与分享。
- iOS学习——UIView的研究
- iOS学习——布局利器Masonry框架源码深度剖析
- iOS项目——自定义UITabBar与布局
- @FeignClient中的@RequestMapping也被Spring MVC加载的问题解决
- Golang语言中Path包用法
- Golang中container/list包中的坑
- 关于Golang语言数组索引的有趣现象
- Golang不定参数
- Go并发编程基础(译)
- go-concurrent-programming.md
- Go语言并发模型:以并行处理MD5为例
- golang 使用json 包 实现序列化
- 【远古文章】用 Go 语言来看 Android! 出发, Android, 出发!
- Leaf 游戏服务器框架简介
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- 第84场周赛
- 第86场周赛
- Python3内置函数表.md
- 第88场周赛
- 面试速记之数据库增删改查|附思维导图
- 图深度学习入门教程(十)——深度图互信息模型
- Python3笔试实际操作基础3.md
- python线程(二)代码部分Threading模块
- python threading.current_thread().name和.getName()有什么区别
- Golang是如何操作excel的?
- Typecho配置多国语支持,检查客户端语言自动切换翻译
- NgModule imports定义的运行时数据结构
- 用Windows电脑训练深度学习模型?超详细配置教程来了
- Angular No provider for EffectsRootModule错误消息
- Leetcode No.15 三数之和