前言
为什么需要树这种数据结构
1)数组存储方式的分析
优点:通过下标方式访问元素,速度快。对于有序数组,还可使用二分查找提高检索速度。
缺点:如果要检索具体某个值,或者插入值(按一定顺序)会整体移动,效率较低[示意图]
2)链式存储方式的分析
优点:在一定程度上对数组存储方式有优化(比如:插入一个数值节点,只需要将插入节点,链接到链表中即可,删除效率也很好)。
缺点:在进行检索时,效率仍然较低,比如(检索某个值,需要从头节点开始遍历)【示意图】
3)树存储方式的分析
能提高数据存储,读取的效率,
比如利用 二叉排序树(Binary Sort Tree),既可以保证数据的检索速度,同时也可以保证数据的插入,删除,修改的速度。
二叉树
概念
1)树有很多种,每个节点最多只能有两个子节点的一种形式称为二叉树。
2)二叉树的子节点分为左节点和右节点
3)示意图
4)如果该二叉树的所有叶子节点都在最后一层,并且结点总数=2^n -1, n为层数,则我们称为满二叉树。
5)如果该二叉树的所有叶子节点都在最后一层或者倒数第二层,而且最后一层的叶子节点在左边连续,倒数第二层的叶子节点在右边连续,我们称为完全二叉树
遍历说明
使用前序,中序和后序对下面的二叉树进行遍历.
1)前序遍历:先输出父节点,再遍历左子树和右子树
2)中序遍历:先遍历左子树,再输出父节点,再遍历右子树
3)后序遍历:先遍历左子树,再遍历右子树,最后输出父节点
4)小结:看输出父节点的顺序,就确定是前序,中序还是后序
思路
代码
package com.sgg.datastructure.tree;public class HeroNode {private int no;private String name;private HeroNode left; //默认 nullprivate HeroNode right; //默认 nullpublic HeroNode(int no, String name) {this.no = no;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;}@Overridepublic String toString() {return "HeroNode{" +"no=" + no +", name='" + name + '\'' +'}';}//自己,左,右public void preOrder() {System.out.println(this);if (getLeft() != null) {getLeft().preOrder();}if (getRight() != null) {getRight().preOrder();}}//左,自己,右public void infixOrder() {if (getLeft() != null) {getLeft().infixOrder();}System.out.println(this);if (getRight() != null) {getRight().infixOrder();}}//左,右,自己public void postOrder() {if (getLeft() != null) {getLeft().postOrder();}if (getRight() != null) {getRight().postOrder();}System.out.println(this);}//查找:前序,中序,后序public HeroNode searchByPre(int no) {if (this.no == no) {return this;}HeroNode result = null;if (this.left != null) {result = left.searchByPre(no);}if (result != null) {return result;}if (this.right != null) {result = right.searchByPre(no);}return result;}public HeroNode searchByMiddle(int no) {HeroNode result = null;if (this.left != null) {result = left.searchByMiddle(no);}if (result != null) {return result;}if (this.no == no) {return this;}if (this.right != null) {result = right.searchByMiddle(no);}return result;}public HeroNode searchByPost(int no) {HeroNode result = null;if (this.left != null) {result = left.searchByPost(no);}if (result != null) {return result;}if (this.right != null) {result = right.searchByPost(no);}if (result != null) {return result;}if (this.no == no) {return this;}return null;}//删除public void delete(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.delete(no);}if (this.right != null) {this.right.delete(no);}}}
package com.sgg.datastructure.tree;public class BinaryTree {private HeroNode root;public void setRoot(HeroNode root) {this.root = root;}//前序遍历public void preOrder() {if (this.root != null) {this.root.preOrder();} else {System.out.println("二叉树为空,无法遍历");}}//中序遍历public void infixOrder() {if (this.root != null) {this.root.infixOrder();} else {System.out.println("二叉树为空,无法遍历");}}//后序遍历public void postOrder() {if (this.root != null) {this.root.postOrder();} else {System.out.println("二叉树为空,无法遍历");}}public HeroNode searchByPre(int no) {return this.root.searchByPre(no);}public HeroNode searchByMiddle(int no) {return this.root.searchByMiddle(no);}public HeroNode searchByPost(int no) {return this.root.searchByPost(no);}public void delete(int no) {this.root.delete(no);}public static void main(String[] args) {//先需要创建一颗二叉树BinaryTree binaryTree = new BinaryTree();//创建需要的结点HeroNode root = new HeroNode(1, "宋江");HeroNode node2 = new HeroNode(2, "吴用");HeroNode node3 = new HeroNode(3, "卢俊义");HeroNode node4 = new HeroNode(4, "林冲");HeroNode node5 = new HeroNode(5, "关胜");//说明,我们先手动创建该二叉树,后面我们学习递归的方式创建二叉树root.setLeft(node2);root.setRight(node3);node3.setRight(node4);node3.setLeft(node5);binaryTree.setRoot(root);//测试System.out.println("前序遍历"); // 1,2,3,5,4binaryTree.preOrder();//测试System.out.println("中序遍历");binaryTree.infixOrder(); // 2,1,5,3,4//System.out.println("后序遍历");binaryTree.postOrder(); // 2,5,4,3,1}}
顺序存储二叉树
概念
从数据存储来看,数组存储方式和树的存储方式可以相互转换,即数组可以转换成树,树也可以转换成数组,看右面的示意图
要求
1)右图的二叉树的结点,要求以数组的方式来存放arr : [1, 2, 3, 4, 5, 6, 6]
2)要求在遍历数组arr时,仍然可以以前序遍历,中序遍历和后序遍历的方式完成结点的遍历
特点
1)顺序二叉树通常只考虑完全二叉树
2)第n个元素的左子节点为2 n + 1
3)第n个元素的右子节点为2 n + 2
4)第n个元素的父节点为(n-1) / 2
5)n :表示二叉树中的第几个元素(按0开始编号如图所示)
代码
package com.sgg.datastructure.tree;public class ArrayBinaryTree {public static int[] arr = {1, 2, 3, 4, 5, 6, 7};public static void main(String[] args) {// preOrder(); // 1,2,4,5,3,6,7// middleOrder();//4 2 5 1 6 3 7postOrder();// 4 5 2 6 7 3 1}public static void preOrder() {preOrder(0);}public static void preOrder(int index) {System.out.println(arr[index]);if (2 * index + 1 < arr.length) {preOrder(2 * index + 1);}if (2 * index + 2 < arr.length) {preOrder(2 * index + 2);}}public static void middleOrder() {middleOrder(0);}private static void middleOrder(int index) {if (2 * index + 1 < arr.length) {middleOrder(2 * index + 1);}System.out.println(arr[index]);if (2 * index + 2 < arr.length) {middleOrder(2 * index + 2);}}public static void postOrder() {postOrder(0);}private static void postOrder(int index) {if (2 * index + 1 < arr.length) {postOrder(2 * index + 1);}if (2 * index + 2 < arr.length) {postOrder(2 * index + 2);}System.out.println(arr[index]);}}
线索化二叉树
概念
引入问题
将数列{1, 3, 6, 8, 10, 14}构建成一颗二叉树.n+1=7
问题分析:
1)当我们对上面的二叉树进行中序遍历时,数列为{8, 3, 10, 1, 14, 6 }
2)但是6, 8, 10, 14这几个节点的 左右指针,并没有完全的利用上.
3)如果我们希望充分的利用 各个节点的左右指针, 让各个节点可以指向自己的前后节点,怎么办?
4)解决方案-线索二叉树
基本介绍
n个结点的二叉链表中含有n+1【公式2n-(n-1)=n+1】 个空指针域。利用二叉链表中的空指针域,存放指向该结点在某种遍历次序下的前驱和后继结点的指针(这种附加的指针称为”线索”)
这种加上了线索的二叉链表称为线索链表,相应的二叉树称为线索二叉树(Threaded BinaryTree)。根据线索性质的不同,线索二叉树可分为前序线索二叉树、中序线索二叉树和后序线索二叉树三种
一个结点的前一个结点,称为前驱结点
-
图解
思路
说明:当线索化二叉树后,Node节点的 属性left和right,有如下情况:
1)left指向的是左子树,也可能是指向的前驱节点.比如 ① 节点left指向的左子树,而 ⑩ 节点的left指向的就是前驱节点.
2)right指向的是右子树,也可能是指向后继节点,比如 ① 节点right指向的是右子树,而⑩ 节点的right指向的是后继节点代码
```java package com.sgg.datastructure.tree;
// 中序的 public class ThreadedBinaryTree { private HeroNode root; private HeroNode pre;
public void setRoot(HeroNode root) {this.root = root;}public static void main(String[] args) {//测试一把中序线索二叉树的功能HeroNode root = new HeroNode(1, "tom");HeroNode node2 = new HeroNode(3, "jack");HeroNode node3 = new HeroNode(6, "smith");HeroNode node4 = new HeroNode(8, "mary");HeroNode node5 = new HeroNode(10, "king");HeroNode node6 = new HeroNode(14, "dim");//二叉树,后面我们要递归创建, 现在简单处理使用手动创建root.setLeft(node2);root.setRight(node3);node2.setLeft(node4);node2.setRight(node5);node3.setLeft(node6);ThreadedBinaryTree threadedBinaryTree = new ThreadedBinaryTree();threadedBinaryTree.setRoot(root);threadedBinaryTree.threadedNodes();System.out.println(node5.getLeft());System.out.println(node5.getRight());System.out.println();threadedBinaryTree.threadedList();}//构建线索化二叉树(中序)public void threadedNodes() {threadedNodes(root);}public void threadedNodes(HeroNode node) {if (node == null) {return;}//(一)先线索化左子树threadedNodes(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;//(三)线索化右子树threadedNodes(node.getRight());}//遍历线索化二叉树的方法(中序)public void threadedList() {//定义一个变量,存储当前遍历的结点,从 root 开始HeroNode node = root;while (node != null) {//循环的找到 leftType == 1 的结点,第一个找到就是 8 结点,后面随着遍历而变化,因为当 leftType==1 时,说明该结点是按照线索化处理后的有效结点while (node.getLeftType() == 0) {node = node.getLeft();}//打印当前这个结点System.out.println(node);//如果当前结点的右指针指向的是后继结点,就一直输出while (node.getRightType() == 1) {//获取到当前结点的后继结点node = node.getRight();System.out.println(node);}//替换这个遍历的结点node = node.getRight();}}
}
```

