学习目标:
掌握二分搜索树学习内容:
1、 树结构 2、 二分搜索树的基础 3、 二分搜索树的操作 4、 二分搜索树的遍历学习时间:
2021年9月12日学习产出:
1、 技术笔记 1 遍 2、CSDN 技术博客 1 篇树结构
为什么要有树结构
树结构本身是一种天然的组织结构,如果用树结构来存储数据,由它的关系可以是查找更加高效,数据也可以更有逻辑
二分搜索树的基础
二分搜索树的结点定义
class Node {
T val;//结点的值
Node left;//左孩子
Node right;//右孩子
public Node(T val) {//二叉树的构造方法
this.val = val;
this.left = this.right = null;
}
}
二分搜索树和链表一样都是动态的数据结构
结点特性
二叉树只有一个根结点
二叉树的结点最多有两个孩子
二叉树的结点最多有一个父亲
![[结点特性]](https://img-blog.csdnimg.cn/d62b14310b9d4030b6010e9b349d2cd9.png?x-oss-process=image/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA6YeR5riF5rO9,size_20,color_FFFFFF,t_70,g_se,x_16)
二叉树的重要特性
二叉树具有天然的递归结构
每个结点的左子树也是二叉树
每个结点的右子树也是二叉树
满二叉树
叶子结点只出现在最底层,除了叶子结点外,其余结点都有两个孩子结点
一个层数为k 的满二叉树总结点数为:2^k-1
第i层上的结点个数为2^(i-1)
一个层数为k的满二叉树的叶子结点个数为2^(k-1)
二叉树不一定是满的
二分搜索树
二分搜索树是二叉树
二分搜索树每个的结点值:
大于其左子树的所有结点的值
小于其右子树的所有结点的值
每一棵子树也都是二分搜索树
其所存储的元素也都具有可比较性
二分搜索树的操作
构建根结点
// 定义根结点
private Node root;
int size;
public BinarySearch() {
this.root = null;
this.size = 0;
}
//判断树是否为空,使用根结点判断
public boolean isEmpty() {
return this.root == null;
}
添加元素
// 向树中添加结点
public void add(T ele) {
// 使用递归的方式添加结点
root = add(root, ele);
this.size += 1;
}
//递归操作
private Node add(Node node, T ele) {
// 递归到底的情况
if (node == null) {
Node node1 = new Node(ele);
return node1;
}
// 递归操作
if (ele.compareTo(root.val) > 0) {
node.right = add(node.right, ele);
} else {
node.left = add(node.left, ele);
}
return node;
}
从树中查找结点
// 从树中查找结点
public Node contains(T ele) {
return contains(root, ele);
}
//递归操作
private Node contains(Node node, T ele) {
// 递归到底的情况
if (node == null) {
return null;
}
// 递归操作
T val = node.val;
if (ele.compareTo(val) == 0) {
return node;
} else if (ele.compareTo(val) > 0) {
return contains(node.right, ele);
} else {
return contains(node.left, ele);
}
}
查找最小结点
不适用递归的做法一直往左子树找
// 查找最小结点
public T findMixNode() {
if (root == null) {
return null;
}
Node curNode = root;
while (curNode.left != null) {
curNode = curNode.left;
}
return curNode.val;
}
使用递归的做法
public T findMixNodeDG() {
if (root == null) {
return null;
}
return findMixNodeDG(root).val;
}
private Node findMixNodeDG(Node root) {
// 递归到底的情况
if (root.left == null) {
return root;
}
// 递归操作
return findMixNodeDG(root.left);
}
删除最小结点
// 删除最小结点
private void removeMixNode() {
//先找到最小结点
T mixNodeVal = findMixNodeDG();
if (mixNodeVal == null) {
System.out.println("the tree is empty");
return;
}
System.out.println("Find mixNode value is " + mixNodeVal.toString());
// 进行删除操作
root = removeMixNode(root);
this.size -= 1;
}
private Node removeMixNode(Node node) {
if (node.left == null) {
// 进行删除操作
Node rightNode = node.right;
node.right = null;
return rightNode;
}
node.left = removeMixNode(node.left);
return node;
}
删除任意结点
删除结点需要考虑条件:
1、删除只有右节点的情况
2、删除只有左节点的情况
3、左右孩子都不为空的情况
第三种情况下,找到需要删除结点的右子树中的最小结点,与删除结点替换
// 删除任意结点
public T removeNode(T val) {
if (root == null) {
System.out.println("The tree is null");
return null;
}
// 查找结点
Node node = findNodeDG(root, val);
if (node != null) {
//删除操作
root = removeNode(root, val);
this.size -= 1;
return node.val;
}
return null;
}
/**
* 从以root为根的二分搜索树中删除值为val的结点
*
* @param node 根结点
* @param val 值
* @return 删除之后二分搜索树的根结点
*/
private Node removeNode(Node node, T val) {
// 递归到底的情况,找到了删除的结点
if (node.val.compareTo(val) == 0) {
if (node.left == null) {
Node rightNode = node.right;
node.right = null;
return rightNode;
} else if (node.right == null) {
Node leftNode = node.left;
node.left = null;
return leftNode;
} else {
// 左右都不为空
// 1、找node的后继(node.right中的最小结点)
Node s = findMixNodeDG(node.right);
// 2、从node.right中删除最小结点
Node rightRoot = removeMixNode(node.right);
// 3、使用后继结点替换node
s.left = node.left;
s.right = rightRoot;
// 4、生成新树,新树的根结点就是后继结点,返回新树的根结点
node.left = node.right = null;
return s;
}
}
// 递归操作
if (node.val.compareTo(val) > 0) {
node.left = removeNode(node.left, val);
} else {
node.right = removeNode(node.right, val);
}
return node;
}
二分搜索树的遍历
遍历操作:把所有结点都访问一遍
前序遍历
先输出根结点,再遍历左子树,再遍历右子树
// 前序遍历
public List<T> preOrder() {
List<T> result = new ArrayList<>();
preOrder(root, result);
return result;
}
private void preOrder(Node root, List<T> result) {
// 递归到底的情况
if (root == null) {
return;
}
// 先获取中间值,再遍历左树,然后遍历右树
result.add(root.val);
preOrder(root.left, result);
preOrder(root.right, result);
}
中序遍历
先遍历左子树,输出中间值,再遍历右子树
// 中序遍历
public List<T> middleOrder() {
List<T> result = new ArrayList<>();
middleOrder(root, result);
return result;
}
private void middleOrder(Node root, List<T> result) {
// 递归到底的情况
if (root == null) {
return;
}
// 先遍历左树,在获取中间值,然后遍历右树
middleOrder(root.left, result);
result.add(root.val);
middleOrder(root.right, result);
}
后序遍历
先遍历左子树,再遍历右子树,最后输出中间值
// 后序遍历
public List<T> subOrder() {
List<T> result = new ArrayList<>();
subOrder(root, result);
return result;
}
private void subOrder(Node root, List<T> result) {
// 递归到底的情况
if (root == null) {
return;
}
// 先遍历左树,再遍历右树,然后获取中间值
subOrder(root.left, result);
subOrder(root.right, result);
result.add(root.val);
}
层序遍历(广度优先)
将最后的输出结构用List集合存储
创建一个队列放入根结点,每次操作都是取出队首放入集合(也就是在这树中我们需要前序输出的结点)
如果这个结点的孩子不为空的话,就以从左到右的形式放入队列
一直循环到队列中没有元素结束
// 层序编列(广度优先)
public List<T> layerOrder() {
List<T> list = new ArrayList<>();
if (root != null) {
Queue<Node> queue = new LinkedList<>();
queue.add(root);
while (!queue.isEmpty()) {
// 从队列中取出队首元素
Node node = queue.poll();
list.add(node.val);
if (node.left != null) {
queue.offer(node.left);
}
if (node.right != null) {
queue.offer(node.right);
}
}
}
return list;
}
课后巩固
力扣102. 二叉树的层序遍历
力扣108. 将有序数组转换为二叉搜索树