为什么要引入树?
- 数组存储方式:由于数组访问元素速度快,开可以使用二分查找提高速度,但是在按一定顺序插入新元素时,当数组满了再插入新元素时,效率低速度慢。
- 链式存储方式:链式存储插入很方便,删除效率也高。但是在检索时,效率也比较低,每次都需要从头遍历到尾。
- 树结构存储方式:能提高数据存储,读取效率。既可以保证检索速度,也可以保证数据的插入、删除、修改的速度。
二叉树
1、概念
1)二叉树
- 每个节点最多只能有两个子节点的一种形式称为二叉树
- 二叉树的子节点分为左节点和右节点
2)满二叉树、完全二叉树
**满二叉树:**所有叶子节点都在最后一层,并且总节点数为2n-1(n为层数)
**完全二叉树:**叶子节点在最后一层和倒数第二层,并且最后一层的叶子节点在左边连续,倒数第二层叶子节点在右边连续
3)前序遍历、中序遍历、后序遍历
前序遍历:先输出父节点、再输出左子树、右子树
中序遍历:先遍历左子树、再遍历父节点、再遍历右子树
后序遍历:先遍历左子树、再遍历右子树、再遍历父节点
tips:遍历的顺序根据父节点的顺序而定
2、前中后序遍历
代码实现
public class BinaryTreeDemo {
public static void main(String[] args) {
// 创建节点
EmpNode node1 = new EmpNode(1,"heroc");
EmpNode node2 = new EmpNode(2,"lucy");
EmpNode node3 = new EmpNode(3,"smith");
EmpNode node4 = new EmpNode(4,"tom");
EmpNode node5 = new EmpNode(5,"jack");
// 创建树
node1.setLeft(node2);
node2.setLeft(node3);
node1.setRight(node4);
node4.setLeft(node5);
// 确定根节点,遍历树
BinaryTree rootNode = new BinaryTree(node1);
// System.out.println("前序:");
// rootNode.perOrder();
// System.out.println("中序:");
// rootNode.centerOrder();
// System.out.println("后序:");
// rootNode.afterOrder();
// 前序遍历查询
EmpNode empNode = rootNode.perOrderFind(5);
if (empNode!=null){
System.out.println(empNode.toString());
}
// 中序遍历查询
empNode = rootNode.perOrderFind(2);
if (empNode!=null){
System.out.println(empNode.toString());
}
// 后序遍历查询
empNode = rootNode.afterOrderFind(4);
if (empNode!=null){
System.out.println(empNode.toString());
}
}
}
// 创建节点
class EmpNode{
private int id;
private String name;
private EmpNode left;
private EmpNode right;
public EmpNode(int id, String name) {
this.id = id;
this.name = name;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public EmpNode getLeft() {
return left;
}
public void setLeft(EmpNode left) {
this.left = left;
}
public EmpNode getRight() {
return right;
}
public void setRight(EmpNode right) {
this.right = right;
}
@Override
public String toString() {
return "EmpNode{" +
"id=" + id +
", name='" + name + '\'' +
'}';
}
// 前序遍历
public void perOrder(){
System.out.println(this.toString());
if (this.left!=null){
this.left.perOrder();
}
if (this.right!=null){
this.right.perOrder();
}
}
// 中序遍历
public void centerOrder(){
if (this.left!=null){
this.left.centerOrder();
}
System.out.println(this.toString());
if (this.right!=null){
this.right.centerOrder();
}
}
// 后续遍历
public void afterOrder(){
if (this.left!=null){
this.left.afterOrder();
}
if (this.right!=null){
this.right.afterOrder();
}
System.out.println(this.toString());
}
// 前序遍历查询
public EmpNode perOrderFind(int id){
if (this.id == id){
return this;
}
EmpNode empNode = null;
if (this.left!=null){
empNode = this.left.perOrderFind(id);
}
if (empNode!=null){
return empNode;
}
if (this.right!=null){
empNode = this.right.perOrderFind(id);
}
if (empNode!=null){
return empNode;
}
return null;
}
// 中序遍历查询
public EmpNode centerOrderFind(int id){
EmpNode empNode = null;
// 向左查找
if (this.left!=null){
empNode = this.left.centerOrderFind(id);
}
if (empNode!=null){ // 如果empNode不等null,说明找到了,就返回结果
return empNode;
}
// 当前节点
if (this.id == id){
return this;
}
// 向右查找
if (this.right != null){
empNode = this.right.centerOrderFind(id);
}
if (empNode!=null){
return empNode;
}
// 都没找到返回null
return empNode;
}
// 后序遍历查询
public EmpNode afterOrderFind(int id){
EmpNode empNode = null;
// 向左查找
if (this.left!=null){
empNode = this.left.centerOrderFind(id);
}
if (empNode!=null){ // 如果empNode不等null,说明找到了,就返回结果
return empNode;
}
// 向右查找
if (this.right != null){
empNode = this.right.centerOrderFind(id);
}
if (empNode!=null){
return empNode;
}
// 当前节点
if (this.id == id){
return this;
}
return empNode;
}
}
// 创建根节点
class BinaryTree{
private EmpNode root;
public BinaryTree(EmpNode root) {
this.root = root;
}
public void perOrder(){
if (root!=null){
root.perOrder();
}else {
System.out.println("树为空");
}
}
public void centerOrder(){
if (root!=null){
root.centerOrder();
}else {
System.out.println("树为空");
}
}
public void afterOrder(){
if (root!=null){
root.afterOrder();
}else {
System.out.println("树为空");
}
}
public EmpNode perOrderFind(int id){
if (root!=null){
return root.perOrderFind(id);
}else {
System.out.println("树为空");
return null;
}
}
public EmpNode centerOrderFind(int id){
if (root!=null){
return root.centerOrderFind(id);
}else {
System.out.println("树为空");
return null;
}
}
public EmpNode afterOrderFind(int id){
if (root!=null){
return root.afterOrderFind(id);
}else {
System.out.println("树为空");
return null;
}
}
}
3、顺序存储二叉树
顺序存储二叉树,是将二叉树顺序存储到数组中,并在遍历数组的时候依旧按照前序遍历、中序遍历、后序遍历。
顺序存储二叉树的特点:
- 顺序二叉树通常只考虑完全二叉树
- 从0开始,第n个元素的左子节点为第2*n+1个元素
- 从0开始,第n个元素的右子节点为第2*n+2个元素
- 从0开始,第n个元素的父节点为第**(n-1)/2**个元素
从0开始计算,为了与数组索引保持一致。
代码实现
public class ArrayBinaryTreeDemo {
public static void main(String[] args) {
int arr[] = {1,2,3,4,5,6,7}; // 这是一个二叉树从根节点依次顺序存储的数组
ArrayBinaryTree arrayBinaryTree = new ArrayBinaryTree(arr);
System.out.println("前序遍历:");
arrayBinaryTree.perOrder();
System.out.println("\n中序遍历:");
arrayBinaryTree.infixOrder();
System.out.println("\n后序遍历:");
arrayBinaryTree.suffixOrder();
}
}
class ArrayBinaryTree{
int arr[];
public ArrayBinaryTree(int[] arr) {
this.arr = arr;
}
// 前序遍历顺序存储的二叉树数组
public void perOrder(){
perOrder(0);
}
public void perOrder(int index){
System.out.print(arr[index]+" ");
if ((2*index+1) < arr.length){
perOrder(2*index+1);
}
if ((2*index+2) < arr.length){
perOrder(2*index+2);
}
}
// 中序遍历顺序存储的二叉树数组
public void infixOrder(){
infixOrder(0);
}
public void infixOrder(int index){
if ((2*index+1) < arr.length){
infixOrder(2*index+1);
}
System.out.print(arr[index]+" ");
if ((2*index+2) < arr.length){
infixOrder(2*index+2);
}
}
// 后序遍历顺序存储的二叉树数组
public void suffixOrder(){
suffixOrder(0);
}
public void suffixOrder(int index){
if ((2*index+1) < arr.length){
suffixOrder(2*index+1);
}
if ((2*index+2) < arr.length){
suffixOrder(2*index+2);
}
System.out.print(arr[index]+" ");
}
}
3、线索化二叉树
由于树在最后的叶子节点都有左右指针是空的,即总共有n个节点的二叉链表中含有n+1个空指针域。利用空指针域存放指向当前节点在某种遍历下的前驱或后继节点。这种附加的指针指向的操作就是线索。
根据线索的不同,线索二叉树又可分为前序线索二叉树、中序线索二叉树、后序线索二叉树。
一个节点的前一个节点,称为前驱节点
一个节点的后一个节点,称为后继节点
中序线索二叉树遍历顺序,infixThreadList()实现:
public class ThreadBinaryTreeDemo {
public static void main(String[] args) {
Node node1 = new Node(1);
Node node2 = new Node(3);
Node node3 = new Node(6);
Node node4 = new Node(8);
Node node5 = new Node(10);
Node node6 = new Node(14);
node1.setLeft(node2);
node1.setRight(node3);
node2.setLeft(node4);
node2.setRight(node5);
node3.setLeft(node6);
ThreadBinaryTree threadBinaryTree = new ThreadBinaryTree(node1);
// threadBinaryTree.perThread(); // 前序线索化二叉树
// threadBinaryTree.perOrder(); // 通过前序遍历
threadBinaryTree.infixThread(); // 中序线索化二叉树
// threadBinaryTree.infixOrder(); // 通过中序遍历
threadBinaryTree.infixThreadList();
System.out.println("id为8节点的后继节点为:"+node4.getRight());
System.out.println("id为10节点的前驱节点为:"+node5.getLeft());
System.out.println("id为10节点的后继节点为:"+node5.getRight());
System.out.println("id为14节点的前驱节点为:"+node6.getLeft());
System.out.println("id为14节点的后继节点为:"+node6.getRight());
}
}
class Node{
private int id;
private Node left;
private Node right;
// 设置左右节点的类型,0为左节点或右节点,1为前驱节点或后继节点
private int leftType;
private int rightType;
public Node(int id) {
this.id = id;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
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;
}
public int getLeftType() {
return leftType;
}
public void setLeftType(int leftType) {
this.leftType = leftType;
}
public int getRightType() {
return rightType;
}
public void setRightType(int rightType) {
this.rightType = rightType;
}
@Override
public String toString() {
return "Node{" +
"id=" + id +
", leftType=" + leftType +
", rightType=" + rightType +
'}';
}
// 前序遍历:只遍历左子节点和右子节点,不遍历前驱节点和后继节点
public void perOrder(){
System.out.println(this.toString());
if (this.left!=null && this.getLeftType()==0){
this.left.perOrder();
}
if (this.right!=null && this.getRightType()==0){
this.right.perOrder();
}
}
// 中序遍历:只遍历左子节点和右子节点,不遍历前驱节点和后继节点
public void infixOrder(){
if (this.left!=null && this.leftType == 0){
this.left.infixOrder();
}
System.out.println(this.toString());
if (this.right!=null && this.rightType == 0){
this.right.infixOrder();
}
}
// 后序遍历:只遍历左子节点和右子节点,不遍历前驱节点和后继节点
public void suffixOrder(){
if (this.left!=null && this.leftType == 0){
this.left.suffixOrder();
}
if (this.right!=null && this.rightType == 0){
this.right.suffixOrder();
}
System.out.println(this.toString());
}
}
class ThreadBinaryTree{
private Node root;
// 用于记录前驱节点,每要遍历下一个节点时,
// 就需要将当前节点记录下来,作为下一个节点的前驱节点
// 如果前驱节点的右子节点为空,那么就可以设置前驱节点的后继节点
// 后继节点也就是当前节点
private Node pre;
public ThreadBinaryTree(Node node) {
this.root = node;
}
// 创建一个前序索引
public void perThread(){
perThread(root);
}
public void perThread(Node node){
if (node == null){
return;
}
// 处理当前节点
// 先处理左节点,如果左子节点为空,那么就将上一个节点设置为左节点的前驱节点,
// 这里不判断pre是否为空,是没有关系的,如果pre为空,说明该节点是线索化的第一个节点
// 有利于根据线索指针进行遍历
if (node.getLeft() == null){
node.setLeft(pre);
node.setLeftType(1);
}
// 由于后继节点需要遍历到下一个节点才能确定,所以,遍历到下一个节点时
// 才能设置前驱节点的右子节点指向当前的node
if (pre!=null && pre.getRight() == null){
pre.setRight(node);
pre.setRightType(1);
}
// 用于记录前驱节点,每要遍历下一个节点时,
// 就需要将当前节点记录下来,作为下一个节点的前驱节点
pre = node;
// 获取左节点,一定要判断是否是原节点,否则会陷入死循环
if (node.getLeftType()==0){
perThread(node.getLeft());
}
// 获取右节点,一定要判断是否是原节点,否则会陷入死循环
if (node.getRightType()==0){
perThread(node.getRight());
}
}
// 创建一个中序序列化二叉树
public void infixThread(){
infixThread(root);
}
public void infixThread(Node node){
if (node==null){
return;
}
if (node.getLeftType() == 0){
infixThread(node.getLeft());
}
if (node.getLeft() == null){
node.setLeft(pre);
node.setLeftType(1);
}
if (pre!=null && pre.getRight() == null){
pre.setRight(node);
pre.setRightType(1);
}
pre = node;
if (node.getRightType()==0){
infixThread(node.getRight());
}
}
// 创建一个后序线索化
public void suffixThread(){
}
// 根据线索指针进行前序线索二叉树遍历
public void perThreadList(){
Node node = root;
while (node!=null){
while (node.getLeftType() == 0){
System.out.println(node.toString());
node = node.getLeft();
}
System.out.println(node.toString());
while (node.getRightType()==1){
node = node.getRight();
System.out.println(node.toString());
}
node = node.getRight();
}
}
// 根据线索指针进行中序线索二叉树遍历
public void infixThreadList(){
// 定义一个变量,存储当前节点
Node node = root;
while (node != null){
// 第一次是要找到左节点为空,并且左节点类型为1,也就是中序线索化的二叉树第一个节点
while (node.getLeftType() == 0){
node = node.getLeft();
}
// 找到了就输出
System.out.println(node.toString());
while (node.getRightType() == 1){
node = node.getRight();
System.out.println(node.toString());
}
node = node.getRight();
}
}
// 根据线索指针进行后序线索二叉树遍历
public void suffixThreadList(){
}
// 前序遍历形式遍历线索二叉树
public void perOrder(){
if (root!=null){
root.perOrder();
}else {
System.out.println("二叉树为空");
}
}
// 中序遍历形式遍历线索二叉树
public void infixOrder(){
if (root!=null){
root.infixOrder();
}else {
System.out.println("二叉树为空");
}
}
// 后序遍历形式遍历线索二叉树
public void suffixOrder(){
if (root!=null){
root.suffixOrder();
}else {
System.out.println("二叉树为空");
}
}
}
结果:
Node{id=8, leftType=0, rightType=1}
Node{id=3, leftType=0, rightType=0}
Node{id=10, leftType=1, rightType=1}
Node{id=1, leftType=0, rightType=0}
Node{id=14, leftType=1, rightType=1}
Node{id=6, leftType=0, rightType=0}
id为8节点的后继节点为:Node{id=3, leftType=0, rightType=0}
id为10节点的前驱节点为:Node{id=3, leftType=0, rightType=0}
id为10节点的后继节点为:Node{id=1, leftType=0, rightType=0}
id为14节点的前驱节点为:Node{id=1, leftType=0, rightType=0}
id为14节点的后继节点为:Node{id=6, leftType=0, rightType=0}