树的存储结构
- 优点:提高了数据存储,读取的效率, 比如利用 二叉排序树(Binary Sort Tree),既可以保证数据的检索速度,同时也 可以保证数据的插入,删除,修改的速度。
- 缺点:顺序存储可能会浪费空间(在非完全二叉树的时候),但是读取某个指定的节点的时候效率比较高O(0)链式存储相对二叉树比较大的时候浪费空间较少,但是读取某个指定节点的时候效率偏低O(nlogn)
树的相关定义:
- 节点:是数据结构中,用来描述“树”型结构的名词。
- 叶结点或终端结点:度为0的结点称为叶结点。
- 父节点:若一个结点含有子结点,则这个结点称为其子结点的父结点;
- 子节点:一个结点含有的子树的根结点称为该结点的子结点;
- 叶子节点 :没有子节点的节点
- 节点的权:节点值
- 路径:从 root 节点找到该节点的路线
- 层:从根开始定义起,根为第1层,根的子结点为第2层,以此类推;
- 子树:以某结点为根的子树中任一结点都称为该结点的子孙。
- 树的高度(最大层数):树中结点的最大层次
- 森林 :多颗子树构成森林
- 树的度:一棵树中,最大的结点的度称为树的度
种类
- 无序树:树中任意节点的子结点之间没有顺序关系,这种树称为无序树,也称为自由树;
- 有序树:树中任意节点的子结点之间有顺序关系,这种树称为有序树;
- 二叉树:每个节点最多含有两个子树的树称为二叉树;
- 满二叉树:叶节点除外的所有节点均含有两个子树的树被称为满二叉树
- 完全二叉树:有个节点的满二叉树称为完全二叉树
- 哈夫曼树(最优二叉树):带权路径最短的二叉树称为哈夫曼树或最优二叉树;
二叉树的概念
-
树有很多种,每个节点最多只能有两个子节点的一种形式称为二叉树。
-
二叉树的子节点分为左节点和右节点
-
示意图:
-
二叉树遍历的说明:使用前序,中序和后序对下面的二叉树进行遍历.
-
前序遍历: 先输出父节点,再遍历左子树和右子树
-
中序遍历: 先遍历左子树,再输出父节点,再遍历右子树
-
后序遍历: 先遍历左子树,再遍历右子树,最后输出父节点
-
总结: 看输出父节点的顺序,就确定是前序,中序还是后序
简单的二叉树三遍历:
package tree;
public class BinaryTreeDemo {
public static void main(String[] args) {
Tree tree = new Tree();
Node root = new Node(1, "zs1");
Node node1 = new Node(2, "zs2");
Node node2 = new Node(3, "zs3");
Node node3 = new Node(4, "zs4");
Node node4 = new Node(5, "zs5");
root.setLeft(node1);
root.setRight(node2);
node1.setLeft(node3);
node2.setRight(node4);
tree.setNode(root);
System.out.println("前序遍历");
tree.preOrder();
System.out.println("中序遍历");
tree.midOrder();
System.out.println("后序遍历");
tree.postOrder();
}
}
// 创建二叉树
class Tree {
private Node node;
public void setNode(Node node) {
this.node = node;
}
// 前序遍历
public void preOrder() {
if (this.node != null) {
this.node.preOrder();
} else {
System.out.println("二叉树为空,无法遍历");
}
}
// 中序遍历
public void midOrder() {
if (this.node != null) {
this.node.midOrder();
} else {
System.out.println("二叉树为空,无法遍历");
}
}
// 后序遍历
public void postOrder() {
if (this.node != null) {
this.node.postOrder();
} else {
System.out.println("二叉树为空,无法遍历");
}
}
}
// 创建结点
class Node {
private int no;
private String name;
private Node left;
private Node right;
public Node(int no, String name) {
this.no = no;
this.name = name;
}
// 构造方法 set get
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 Node getLeft() {
return left;
}
public void setLeft(Node left) {
this.left = left;
}
public Node getRight() {
return right;
}
public void setRight(Node right) {
this.right = right;
}
// 构造toString 方法
@Override
public String toString() {
return "Node [no=" + no + ", name=" + name + "]";
}
// 前序遍历
public void preOrder() {
System.out.println(this);
if (this.left != null) {
this.left.preOrder();
}
if (this.right != null) {
this.right.preOrder();
}
}
// 中序遍历
public void midOrder() {
if (this.left != null) {
this.left.midOrder();
}
System.out.println(this);
if (this.right != null) {
this.right.midOrder();
}
}
// 后序遍历
public void postOrder() {
if (this.left != null) {
this.left.postOrder();
}
if (this.right != null) {
this.right.postOrder();
}
System.out.println(this);
}
}
查找指定节点
- 根据所给id值查找对应的数
- 思路分析:
分为三种遍历方式:
- 前序遍历:
1)先判断当前结点的id值是否为要查找的值
2)相等就返回 不相等 就先判断左子节点是否为空,不为空则继续前序遍历
3)左子节点如果判断没有要找的结点则继续向右子节点继续遍历查找 - 中序遍历:
1)判断当前结点的左子节点是否为空,不为空就继续遍历
2)找到就返回,没有就和当前结点比较,是返回不是继续遍历
3)右子节点是否为空,找到就返回,不是就置null - 后序遍历:
1)判断当前结点的左子节点是否为空,不为空就继续遍历
2)右子节点是否为空,找到就返回
3)没有就和当前结点比较,是返回不是就置null
package tree;
public class BinaryTreeDemo1 {
public static void main(String[] args) {
Tree1 tree = new Tree1();
Node1 root = new Node1(1, "zs1");
Node1 node1 = new Node1(2, "zs2");
Node1 node2 = new Node1(3, "zs3");
Node1 node3 = new Node1(4, "zs4");
Node1 node4 = new Node1(5, "zs5");
root.setLeft(node1);
root.setRight(node2);
node1.setLeft(node3);
node2.setRight(node4);
tree.setNode(root);
Node1 test = tree.preOrderSearch(1);
if (test != null) {
System.out.printf("找到了,信息为 no=%d name=%s\n", test.getNo(), test.getName());
} else {
System.out.println("信息不存在");
}
Node1 test1 = tree.midOrderSearch(1);
if (test1 != null) {
System.out.printf("找到了,信息为 no=%d name=%s\n", test1.getNo(), test1.getName());
} else {
System.out.println("信息不存在");
}
Node1 test2 = tree.postOrderSearch(1);
if (test2 != null) {
System.out.printf("找到了,信息为 no=%d name=%s\n", test2.getNo(), test2.getName());
} else {
System.out.println("信息不存在");
}
}
}
//创建二叉树
class Tree1 {
private Node1 node;
public void setNode(Node1 root) {
this.node = root;
}
// 前序遍历查找
public Node1 preOrderSearch(int id) {
if (this.node != null) {
return this.node.preOrderSearch(id);
} else {
return null;
}
}
// 中序遍历查找
public Node1 midOrderSearch(int id) {
if (this.node != null) {
return this.node.midOrderSearch(id);
} else {
return null;
}
}
// 后序遍历查找
public Node1 postOrderSearch(int id) {
if (this.node != null) {
return this.node.postOrderSearch(id);
} else {
return null;
}
}
}
// 创建结点
class Node1 {
private int no;
private String name;
private Node1 left;
private Node1 right;
public Node1(int no, String name) {
this.no = no;
this.name = name;
}
// 构造方法 set get
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 Node1 getLeft() {
return left;
}
public void setLeft(Node1 left) {
this.left = left;
}
public Node1 getRight() {
return right;
}
public void setRight(Node1 right) {
this.right = right;
}
// 构造toString 方法
@Override
public String toString() {
return "Node [no=" + no + ", name=" + name + "]";
}
// 前序遍历
public Node1 preOrderSearch(int id) {
System.out.println("进入前序遍历");
if (this.no == id) {
return this;
}
Node1 node = null;
if (this.left != null) {
node = this.left.preOrderSearch(id);
}
if (node != null) {
return node;
}
if (this.right != null) {
node = this.right.preOrderSearch(id);
}
return node;
}
// 中序遍历
public Node1 midOrderSearch(int id) {
System.out.println("进入中序遍历");
Node1 node = null;
if (this.left != null) {
node = this.left.midOrderSearch(id);
}
if (node != null) {
return node;
}
if (this.no == id) {
return this;
}
if (this.right != null) {
this.right.midOrderSearch(id);
}
return node;
}
// 后序遍历
public Node1 postOrderSearch(int id) {
System.out.println("进入后序遍历");
Node1 node = null;
if (this.left != null) {
node = this.left.postOrderSearch(id);
}
if (node != null) {
return node;
}
if (this.no == id) {
return this;
}
if (this.right != null) {
this.right.postOrderSearch(id);
}
return node;
}
}
二叉树删除节点操作
- 需求分析
1.所删除的节点是叶子节点,则直接删除
2.所删除的节点是非叶子节点,则删除该子树
- 思路分析:
1.首先需要考虑此树是否为空树,如果只有一个节点,直接置空即可,否则需要继续考虑:
2.判断左右子节点是否是需要删除的节点,如果是;只需对应左右子节点置空即可,否则需要按照对应的方向进行递归处理,直到遍历完所有的节点。
// 删除节点
public void delNode(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.delNode(no);
}
// 向右递归处理
if (this.right == null) {
this.right.delNode(no);
}
}
顺序存储二叉树
- 需求分析:要求可以按照数组的方式存放对应的数据例如:
arr[] ={1,2,3,4,5}; - 顺序存储数据的特点:
- 顺序二叉树通常只考虑完全二叉树
- 第 n 个元素的左子节点为 2 * n + 1
- 第 n 个元素的右子节点为 2 * n + 2
- 第 n 个元素的父节点为 (n-1) / 2
- n : 表示二叉树中的第几个元素(按 0 开始编号)
package tree;
public class ArrBinaryTreeDemo {
public static void main(String[] args) {
int[] arr = {
1, 2, 3, 4, 5, 6, 7 };
// 创建一个 ArrBinaryTree
ArrBinaryTree ar = new ArrBinaryTree(arr);
System.out.println("前序遍历");
ar.preOrder(0); // 1,2,4,5,3,6,7
System.out.println();
System.out.println("中序遍历");
ar.midOrder(0);
System.out.println();
System.out.println("后序遍历");
ar.postOrder(0);
}
}
// 实现顺序存储二叉树遍历
class ArrBinaryTree {
private int[] arr;
//构造器传入数组
public ArrBinaryTree(int[] arr) {
this.arr = arr;
}
// 顺序存储:前序遍历
public void preOrder(int i) {
// 如果数组为空,或者 arr.length = 0
if (arr == null || arr.length == 0) {
System.out.println("数组为空");
}
// 输出当前这个元素
System.out.print(" " + arr[i]);
// 向左递归遍历
if ((2 * i + 1) < arr.length) {
preOrder(2 * i + 1);
}
// 向右递归遍历
if ((2 * i + 2) < arr.length) {
preOrder(2 * i + 2);
}
}
// 顺序存储:中序遍历
public void midOrder(int i) {
// 如果数组为空,或者 arr.length = 0
if (arr == null || arr.length == 0) {
System.out.println("数组为空");
}
// 向左递归遍历
if ((2 * i + 1) < arr.length) {
preOrder(2 * i + 1);
}
// 输出当前这个元素
System.out.print(" " + arr[i]);
// 向右递归遍历
if ((2 * i + 2) < arr.length) {
preOrder(2 * i + 2);
}
}
// 顺序存储:后序遍历
public void postOrder(int i) {
// 如果数组为空,或者 arr.length = 0
if (arr == null || arr.length == 0) {
System.out.println("数组为空");
}
// 向左递归遍历
if ((2 * i + 1) < arr.length) {
preOrder(2 * i + 1);
}
// 向右递归遍历
if ((2 * i + 2) < arr.length) {
preOrder(2 * i + 2);
}
// 输出当前这个元素
System.out.print(" " + arr[i]);
}
}
线索化二叉树
- 目的:为了使节点的左右指针得到重充分的利用
- 根据线索性质的不同,大致分为:前序线索二叉树、中序线索二叉树和后序线索二叉树三种
- 一个结点的前一个结点,称为前驱结点
- 一个结点的后一个结点,称为后继结点
- n 个结点的二叉链表中含有 n+1个空指针域
【公式 2n-(n-1)=n+1】
//定义ThreadedBinaryTree 实现了线索化功能的二叉树
class ThreadedBinaryTree {
private Node root;
// 为了实现线索化,需要创建要给指向当前结点的前驱结点的指针
// 在递归进行线索化时,pre 总是保留前一个结点
private Node pre = null;
public void setRoot (Node root) {
this.root = root;
}
// 遍历线索化二叉树的方法
public void threadedList() {
// 定义一个变量,存储当前遍历的结点,从root开始
HeroNode node = root;
while (node != null) {
// 循环的找到leftIndex == 1的结点,第一个找到就是8结点
// 后面随着遍历而变化,因为当leftIndex==1时,说明该结点是按照线索化
// 处理后的有效结点
while (node.getLeftIndex() == 0) {
node = node.getLeft();
}
// 打印当前这个结点
System.out.println(node);
// 如果当前结点的右指针指向的是后继结点,就一直输出
while (node.getRightIndex() == 1) {
// 获取到当前结点的后继结点
node = node.getRight();
System.out.println(node);
}
// 替换这个遍历的结点
node = node.getRight();
}
}
因为对二叉树进行线索化操作过后,各个结点指向有变化,新加了前驱和后继结点指向,因此递归的方式不能再继续使用了,需要新的遍历方式:
线型遍历线索二叉树(中序遍历)
public void threadedNode(Node node) {
// 如果node==null, 不能线索化
if (node == null) {
return;
}
//1.线索化左子树
threadedNode(node.getLeft());
// 2.线索化当前结点
// 处理当前结点的前驱结点
if (node.getLeft() == null) {
// 让当前结点的左指针指向前驱结点
node.setLeft(cur);
// 修改当前结点的左指针的类型,指向前驱结点
node.setLeftIndex(1);
}
// 处理当前结点的后继结点
if (cur != null && cur.getRight() == null) {
// 让前驱结点的右指针指向当前结点
cur.setRight(node);
// 修改前驱结点的右指针类型
cur.setRightIndex(1);
}
//每处理一个结点后,重新定义前驱结点,设置当前结点为下一个结点的前驱结点
cur = node;
// 3.线索化右子树
threadedNodes(node.getRight());
}