树结构之基础 应用 查找
树结构
数组存储方式的分析
优点:通过下标方式访问元素,速度快。对于有序数组,还可使用二分查找提高检索速度
缺点:如果要检索具体某个值,或者插入值(按一定顺序)会整体移动,效率较低
链式存储方式的分析
优点:在一定程度上对数组存储方式有优化(比如:插入一个数值节点,只需要将插入节点,链接到链表中即可,
删除效率也很好)。
缺点:在进行检索时,效率仍然较低,比如(检索某个值,需要从头节点开始遍历)
树存储方式的分析
能提高数据存储,读取的效率, 比如利用 二叉排序树(Binary Sort Tree),既可以保证数据的检索速度,同时也
可以保证数据的插入,删除,修改的速度
二叉树
基本概念
- 树有很多种,每个节点最多只能有两个子节点的一种形式称为二叉树
- 二叉树的子节点分为左节点和右节点
- 如果该二叉树的所有叶子节点都在最后一层,并且结点总数= 2^n -1 , n 为层数,则我们称为满二叉树
- 如果该二叉树的所有叶子节点都在最后一层或者倒数第二层,而且最后一层的叶子节点在左边连续,倒数第二层的叶子节点在右边连续,我们称为完全二叉树
二叉树的遍历
使用前序,中序和后序对下面的二叉树进行遍历
- 前序遍历: 先输出父节点,再遍历左子树和右子树
- 中序遍历: 先遍历左子树,再输出父节点,再遍历右子树
- 后序遍历: 先遍历左子树,再遍历右子树,最后输出父节点
小结: 看输出父节点的顺序,就确定是前序,中序还是后序
思路分析:
代码实现:
实现下列二叉树:
package com.atguigu.tree;
/**
* @ClassName BinaryTreeDemo
* @Author Jeri
* @Date 2022-02-22 9:52
* @Description 二叉树
*/
//创建节点类
class HeroNode{
private int no;
private String name;
private HeroNode left;//默认为 null
private HeroNode right;//默认为 null
public HeroNode(int no, String name) {
this.no = no;
this.name = name;
}
public int getNo() {
return no;
}
public void setNo(int no) {
this.no = no;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public HeroNode getLeft() {
return left;
}
public void setLeft(HeroNode left) {
this.left = left;
}
public HeroNode getRight() {
return right;
}
public void setRight(HeroNode right) {
this.right = right;
}
@Override
public String toString() {
return "HeroNode{" +
"no=" + no +
", name='" + name + '\'' +
'}';
}
/*
* @Description 编写前序遍历方法
* @Date 2022/2/22 17:16
* @param
* @return []
*/
public void preOrder(){
//输出当前节点
System.out.println(this);
//向左子树递归遍历
if(this.left != null){
this.left.preOrder();
}
//向右子树递归遍历
if(this.right != null){
this.right.preOrder();
}
}
/*
* @Description 编写中序遍历方法
* @Date 2022/2/22 17:17
* @param
* @return []
*/
public void infixOrder(){
//向左子树递归遍历
if(this.left != null){
this.left.infixOrder();
}
//输出当前节点
System.out.println(this);
//向右子树递归遍历
if(this.right != null){
this.right.infixOrder();
}
}
/*
* @Description 编写后序遍历方法
* @Date 2022/2/22 17:18
* @param
* @return []
*/
public void postOrder(){
//向左子树递归遍历
if(this.left != null){
this.left.postOrder();
}
//向右子树递归遍历
if(this.right != null){
this.right.postOrder();
}
//输出当前节点
System.out.println(this);
}
}
//定义二叉树
class BinaryTree{
private HeroNode root;
public void setRoot(HeroNode root) {
this.root = root;
}
/*
* @Description 前序遍历
* @Date 2022/2/22 17:28
* @param
* @return []
*/
public void preOrder(){
if(this.root != null){
this.root.preOrder();
}else{
System.out.println("当前二叉树为空 无法遍历");
}
}
/*
* @Description 中序遍历
* @Date 2022/2/22 17:28
* @param
* @return []
*/
public void infixOrder(){
if(this.root != null){
this.root.infixOrder();
}else{
System.out.println("当前二叉树为空 无法遍历");
}
}
/*
* @Description 后序遍历
* @Date 2022/2/22 17:28
* @param
* @return []
*/
public void postOrder(){
if(this.root != null){
this.root.postOrder();
}else{
System.out.println("当前二叉树为空 无法遍历");
}
}
}
public class BinaryTreeDemo {
public static void main(String[] args) {
//创建一颗二叉树
BinaryTree bt = new BinaryTree();
//创建需要的节点
HeroNode node1 = new HeroNode(1,"宋江");
HeroNode node2 = new HeroNode(2,"吴用");
HeroNode node3 = new HeroNode(3,"卢俊义");
HeroNode node4 = new HeroNode(4,"林冲");
HeroNode node5 = new HeroNode(5,"关胜");
//手动添加二叉树节点 后面学习递归创建二叉树
bt.setRoot(node1);
node1.setLeft(node2);
node1.setRight(node3);
node3.setRight(node4);
node3.setLeft(node5);
//测试遍历
System.out.println("前序遍历结果:------");
bt.preOrder();
System.out.println();
System.out.println("中序遍历结果:------");
bt.infixOrder();
System.out.println();
System.out.println("后序遍历结果:------");
bt.postOrder();
}
}
前序遍历结果:------
HeroNode{no=1, name='宋江'}
HeroNode{no=2, name='吴用'}
HeroNode{no=3, name='卢俊义'}
HeroNode{no=5, name='关胜'}
HeroNode{no=4, name='林冲'}
中序遍历结果:------
HeroNode{no=2, name='吴用'}
HeroNode{no=1, name='宋江'}
HeroNode{no=5, name='关胜'}
HeroNode{no=3, name='卢俊义'}
HeroNode{no=4, name='林冲'}
后序遍历结果:------
HeroNode{no=2, name='吴用'}
HeroNode{no=5, name='关胜'}
HeroNode{no=4, name='林冲'}
HeroNode{no=3, name='卢俊义'}
HeroNode{no=1, name='宋江'}
二叉树的查找
思路分析:
代码实现:
分别使用三种查找方式,查找 heroNO = 5 的节点
package com.atguigu.tree;
/**
* @ClassName BinaryTreeDemo
* @Author Jeri
* @Date 2022-02-22 9:52
* @Description 二叉树
*/
//创建节点类
class HeroNode{
private int no;
private String name;
private HeroNode left;//默认为 null
private HeroNode right;//默认为 null
public HeroNode(int no, String name) {
this.no = no;
this.name = name;
}
public int getNo() {
return no;
}
public void setNo(int no) {
this.no = no;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public HeroNode getLeft() {
return left;
}
public void setLeft(HeroNode left) {
this.left = left;
}
public HeroNode getRight() {
return right;
}
public void setRight(HeroNode right) {
this.right = right;
}
@Override
public String toString() {
return "HeroNode{" +
"no=" + no +
", name='" + name + '\'' +
'}';
}
/*
* @Description 编写前序遍历方法
* @Date 2022/2/22 17:16
* @param
* @return []
*/
public void preOrder(){
//输出当前节点
System.out.println(this);
//向左子树递归遍历
if(this.left != null){
this.left.preOrder();
}
//向右子树递归遍历
if(this.right != null){
this.right.preOrder();
}
}
/*
* @Description 编写中序遍历方法
* @Date 2022/2/22 17:17
* @param
* @return []
*/
public void infixOrder(){
//向左子树递归遍历
if(this.left != null){
this.left.infixOrder();
}
//输出当前节点
System.out.println(this);
//向右子树递归遍历
if(this.right != null){
this.right.infixOrder();
}
}
/*
* @Description 编写后序遍历方法
* @Date 2022/2/22 17:18
* @param
* @return []
*/
public void postOrder(){
//向左子树递归遍历
if(this.left != null){
this.left.postOrder();
}
//向右子树递归遍历
if(this.right != null){
this.right.postOrder();
}
//输出当前节点
System.out.println(this);
}
/*
* @Description 前序遍历查找
* @Date 2022/2/22 17:18
* @param no 带查找节点的编号
* @return [no]
*/
public HeroNode preOrderSearch(int no){
System.out.println("进行前序遍历查找");
//比较当前节点是否为待查找节点
if(this.no == no){
return this;
}
//resNode 临时变量
HeroNode resNode = null;
//判断当前结点的左子节点是否为空,如果不为空,则递归前序查找
//左递归前序查找 找到节点 则返回 否则 返回 null
if(this.left != null){
resNode = this.left.preOrderSearch(no);
}
//判断左递归前序查找结果
if(resNode != null){
return resNode;
}
//当前的结点的右子节点是否为空,如果不空,则继续向右递归前序查找
if(this.right != null){
resNode = this.right.preOrderSearch(no);
}
//无需判断 直接返回
return resNode;
}
/*
* @Description 中序遍历查找
* @Date 2022/2/22 17:18
* @param no 带查找节点的编号
* @return [no]
*/
public HeroNode infixOrderSearch(int no){
//resNode 临时变量
HeroNode resNode = null;
//判断当前结点的左子节点是否为空,如果不为空,则递归中序查找
if(this.left != null){
resNode = this.left.infixOrderSearch(no);
}
//判断左递归中序查找结果
//如果找到,则返回,如果没有找到,就和当前结点比较,如果是则返回当前结点
if(resNode != null){
return resNode;
}
System.out.println("进行中序遍历查找");
//比较当前节点是否为待查找节点
if(this.no == no){
return this;
}
//否则继续进行右递归的中序查找
if(this.right != null){
resNode = this.right.infixOrderSearch(no);
}
//无需判断 直接返回
return resNode;
}
/*
* @Description 后序遍历查找
* @Date 2022/2/22 17:18
* @param no 带查找节点的编号
* @return [no]
*/
public HeroNode postOrderSearch(int no){
//resNode 临时变量
HeroNode resNode = null;
//判断当前结点的左子节点是否为空,如果不为空,则递归后序查找
if(this.left != null){
resNode = this.left.postOrderSearch(no);
}
//判断左递归后序查找结果
//如果找到,则返回,如果没有找到,向右子树递归进行后序遍历查找
if(resNode != null){
return resNode;
}
//进行右递归的后序查找
if(this.right != null){
resNode = this.right.postOrderSearch(no);
}
//判断右递归后序查找结果
//如果找到,则返回,如果没有找到,就比较当前结点是不是待查找结点
if(resNode != null){
return resNode;
}
System.out.println("进行后序遍历查找");
//比较当前节点是否为待查找节点
if(this.no == no){
return this;
}
//返回结果
return resNode;
}
}
//定义二叉树
class BinaryTree{
private HeroNode root;
public void setRoot(HeroNode root) {
this.root = root;
}
/*
* @Description 前序遍历
* @Date 2022/2/22 17:28
* @param
* @return []
*/
public void preOrder(){
if(this.root != null){
this.root.preOrder();
}else{
System.out.println("当前二叉树为空 无法遍历");
}
}
/*
* @Description 中序遍历
* @Date 2022/2/22 17:28
* @param
* @return []
*/
public void infixOrder(){
if(this.root != null){
this.root.infixOrder();
}else{
System.out.println("当前二叉树为空 无法遍历");
}
}
/*
* @Description 后序遍历
* @Date 2022/2/22 17:28
* @param
* @return []
*/
public void postOrder(){
if(this.root != null){
this.root.postOrder();
}else{
System.out.println("当前二叉树为空 无法遍历");
}
}
/*
* @Description 前序遍历查找
* @Date 2022/2/22 17:30
* @param no 待查找结点的编号
* @return [no]
*/
public HeroNode preOrderSearch(int no){
if(this.root != null){
return this.root.preOrderSearch(no);
}else{
return null;
}
}
/*
* @Description 中序遍历查找
* @Date 2022/2/22 17:30
* @param no 待查找结点的编号
* @return [no]
*/
public HeroNode infixOrderSearch(int no){
if(this.root != null){
return this.root.infixOrderSearch(no);
}else{
return null;
}
}
/*
* @Description 后序遍历查找
* @Date 2022/2/22 17:30
* @param no 待查找结点的编号
* @return [no]
*/
public HeroNode postOrderSearch(int no){
if(this.root != null){
return this.root.postOrderSearch(no);
}else{
return null;
}
}
}
public class BinaryTreeDemo {
public static void main(String[] args) {
//创建一颗二叉树
BinaryTree bt = new BinaryTree();
//创建需要的节点
HeroNode node1 = new HeroNode(1,"宋江");
HeroNode node2 = new HeroNode(2,"吴用");
HeroNode node3 = new HeroNode(3,"卢俊义");
HeroNode node4 = new HeroNode(4,"林冲");
HeroNode node5 = new HeroNode(5,"关胜");
//手动添加二叉树节点 后面学习递归创建二叉树
bt.setRoot(node1);
node1.setLeft(node2);
node1.setRight(node3);
node3.setRight(node4);
node3.setLeft(node5);
//测试查找
HeroNode newNode = new HeroNode(5,"关胜");
//临时变量
HeroNode temp;
System.out.println();
System.out.println("前序遍历查找:------");
temp = bt.preOrderSearch(newNode.getNo());
if(temp == null){
System.out.printf("没有找到编号为%d的人物\n",newNode.getNo());
}else{
System.out.printf("找到了,人物信息为" + temp + "\n");
}
System.out.println();
System.out.println("中序遍历查找:------");
temp = bt.infixOrderSearch(newNode.getNo());
if(temp == null){
System.out.printf("没有找到编号为%d的人物\n",newNode.getNo());
}else{
System.out.printf("找到了,人物信息为" + temp + "\n");
}
System.out.println();
System.out.println("后序遍历查找:------");
temp = bt.postOrderSearch(newNode.getNo());
if(temp == null){
System.out.printf("没有找到编号为%d的人物\n",newNode.getNo());
}else{
System.out.printf("找到了,人物信息为" + temp + "\n");
}
}
}
前序遍历查找:------
进行前序遍历查找
进行前序遍历查找
进行前序遍历查找
进行前序遍历查找
找到了,人物信息为HeroNode{no=5, name='关胜'}
中序遍历查找:------
进行中序遍历查找
进行中序遍历查找
进行中序遍历查找
找到了,人物信息为HeroNode{no=5, name='关胜'}
后序遍历查找:------
进行后序遍历查找
进行后序遍历查找
找到了,人物信息为HeroNode{no=5, name='关胜'}
二叉树的删除(规则1)
思路分析:
要求:
- 如果删除的节点是叶子节点,则删除该节点
- 如果删除的节点是非叶子节点,则删除该子树
- 测试,删除掉 5 号叶子节点 和 3 号子树
代码实现:
HeroNode 类增加方法
/*
* @Description 删除节点
* 1.如果删除的节点是叶子节点,则删除该节点
* 2.如果删除的节点是非叶子节点,则删除该子树
* @Date 2022/2/22 19:55
* @param no 待删除节点的编号
*/
public void deleteByNo(int no){
//左子树是待查找节点
if(this.left != null && this.left.no == no){
this.left = null;
return;
}
//右子树是待查找节点
if(this.right != null &&this.right.no == no){
this.right = null;
return;
}
//向左子树递归删除
if(this.left != null){
this.left.deleteByNo(no);
}
//向右子树递归删除
if(this.right != null){
this.right.deleteByNo(no);
}
}
在 BinaryTree 类增加方法
public void deleteByNo(int no){
if(this.root == null){
System.out.println("空树 无法删除");
}else{
//判断根节点是否为待查找结点
if(this.root.getNo() == no){
root = null;
return;
}
//否则 递归删除
this.root.deleteByNo(no);
}
}
在 BinaryTreeDemo 类增加测试代码:
//测试删除
System.out.println("删除前,前序遍历结果:--------");
bt.preOrder();
System.out.println("删除5号节点后,前序遍历结果为:------");
bt.deleteByNo(5);
bt.preOrder();
删除前,前序遍历结果:--------
HeroNode{no=1, name='宋江'}
HeroNode{no=2, name='吴用'}
HeroNode{no=3, name='卢俊义'}
HeroNode{no=5, name='关胜'}
HeroNode{no=4, name='林冲'}
删除5号节点后,前序遍历结果为:------
HeroNode{no=1, name='宋江'}
HeroNode{no=2, name='吴用'}
HeroNode{no=3, name='卢俊义'}
HeroNode{no=4, name='林冲'}
//测试删除
System.out.println("删除前,前序遍历结果:--------");
bt.preOrder();
// System.out.println("删除5号节点后,前序遍历结果为:------");
bt.deleteByNo(5);
System.out.println("删除3号节点后,前序遍历结果为:------");
bt.deleteByNo(3);
bt.preOrder();
删除前,前序遍历结果:--------
HeroNode{no=1, name='宋江'}
HeroNode{no=2, name='吴用'}
HeroNode{no=3, name='卢俊义'}
HeroNode{no=5, name='关胜'}
HeroNode{no=4, name='林冲'}
删除3号节点后,前序遍历结果为:------
HeroNode{no=1, name='宋江'}
HeroNode{no=2, name='吴用'}
二叉树的删除(规则2)
要求:
- 如果该非叶子节点 A 只有一个子节点B,则子节点 B 替代节点A
- 如果该非叶子节点 A 有左子节点B和右子节点C,则让左子节点B代替节点A
- 如果叶子节点 A 则 直接删除
代码实现:
HeroNode 类增加方法
public void deleteByRule2(int no){
//如果左子节点不为空且为删除节点
if(this.left != null && this.left.no == no){
//定义临时变量 为待删除节点
HeroNode temp = this.left;
//判断 该节点为叶子节点
if(temp.left == null && temp.right == null){
this.left = null;
return;
}
//判断 该节点 同时存在左右子节点
if(temp.left != null && temp.right != null){
this.left = temp.left;
this.left.right = temp.right;
return;
}
//判断只存在左节点
if(temp.left != null && temp.right == null){
this.left = temp.left;
return;
}
//判断只存在右节点
if(temp.left == null && temp.right != null){
this.left = temp.right;
return;
}
}
//如果右子节点不为空且为删除节点
if(this.right != null && this.right.no == no){
//定义临时变量 为待删除节点
HeroNode temp = this.right;
//判断 该节点为叶子节点
if(temp.left == null && temp.right == null){
this.right = null;
return;
}
//判断 该节点 同时存在左右子节点
if(temp.left != null && temp.right != null){
this.right = temp.left;
this.right.right = temp.right;
return;
}
//判断只存在左节点
if(temp.left != null && temp.right == null){
this.right = temp.left;
return;
}
//判断只存在右节点
if(temp.left == null && temp.right != null){
this.right = temp.right;
return;
}
}
//左子树不为空 进行递归删除
if(this.left != null){
this.left.deleteByRule2(no);
}
//向右子树递归删除
if(this.right != null){
this.right.deleteByRule2(no);
}
}
在 BinaryTree 类增加方法
public void deleteByRule2(int no){
if(this.root == null){
System.out.println("空树 无法删除");
}else{
//判断根节点是否为待查找结点
if(this.root.getNo() == no){
//判断 该节点为叶子节点
if(root.getLeft() == null && root.getRight() == null){
root = null;
return;
}
//判断 该节点 同时存在左右子节点
// 删除有些问题
// if(root.getLeft() != null && root.getRight() != null){
// //辅助变量
// HeroNode temp;
// temp = root.getRight();
// setRoot(root.getLeft());
// root.setRight(temp);
// return;
// }
//判断只存在左节点
if(root.getLeft() != null && root.getRight() == null){
setRoot(root.getLeft());
return;
}
//判断只存在右节点
if(root.getLeft() == null && root.getRight() != null){
setRoot(root.getRight());
return;
}
}
//否则 递归删除
this.root.deleteByRule2(no);
}
}
在 BinaryTreeDemo 类增加测试代码:
//测试删除
System.out.println("按照规则2删除前,前序遍历结果:--------");
bt.preOrder();
// System.out.println("删除5号节点后,前序遍历结果为:------");
bt.deleteByNo(5);
System.out.println("按照规则2删除3号节点后,前序遍历结果为:------");
bt.deleteByRule2(3);
bt.preOrder();
按照规则2删除前,前序遍历结果:--------
HeroNode{no=1, name='宋江'}
HeroNode{no=2, name='吴用'}
HeroNode{no=3, name='卢俊义'}
HeroNode{no=5, name='关胜'}
HeroNode{no=4, name='林冲'}
按照规则2删除3号节点后,前序遍历结果为:------
HeroNode{no=1, name='宋江'}
HeroNode{no=2, name='吴用'}
HeroNode{no=5, name='关胜'}
HeroNode{no=4, name='林冲'}
参考文献
尚硅谷Java数据结构与java算法