数组存储方式:
链式存储方式:
树存储方式:
能提高数据存储,读取的效率,比如利用二叉排序树(Binary Sort Tree),既可以保证数据的检索速度,同时也可以保证数据的插入,删除,修改的速度。
示意图:
节点:树中的一个连接点。
根节点(仅有一个):非空树中无前驱节点的节点,称之为根节点。
父节点:若一个节点含有子节点,则这个节点成为其子节点的父节点。
子节点:一个节点含有的子树的根节点成为该节点的子节点。
叶子节点 (没有子节点的节点):一棵树中没有子节点的节点成为叶子节点。
节点的权:节点的具体值。
路径(从 root
节点找到该节点的路线):从根节点到某一个具体节点所走过的路。
层:根节点在1层,其它任一节点的层数是其父节点的层数加1。
子树:只要包含了一个节点,就得包含这个节点下的所有节点。
树的高度(最大层数):树内所有节点高度的最大值,也就是根节点的高度,也就是树的层数。
森林 (多颗子树构成森林):森林是由若干棵树组成,可以将森林中的每棵树的根节点看作是兄弟。
树有很多种,每个节点最多只能有两个子节点的一种形式称为二叉树。
需求:使用前序,中序和后序对二叉树进行遍历和查找,并编写删除节点方法。
前序遍历: 先输出父节点,再遍历左子树和右子树。(父节点 -> 左子树 -> 右子树)
中序遍历: 先遍历左子树,再输出父节点,再遍历右子树。(左子树 -> 父节点 -> 右子树)
后序遍历: 先遍历左子树,再遍历右子树,最后输出父节点。(左子树 -> 右子树 -> 父节点)
小结: 看输出父节点的顺序,就确定是前序,中序还是后序。
代码示例:
public class BinaryTreeDemo {
public static void main(String[] args) {
// 1.创建一棵二叉树。
BinaryTree binaryTree = new BinaryTree();
// 2.创建节点。
Node root = new Node(1, "data1");
Node node2 = new Node(2, "data2");
Node node3 = new Node(3, "data3");
Node node4 = new Node(4, "data4");
Node node5 = new Node(5, "data5");
// 3.手动给二叉树赋值。
root.setLeft(node2);
root.setRight(node3);
node3.setRight(node4);
node3.setLeft(node5);
binaryTree.setRoot(root);
// 4.测试[遍历]。
System.out.println("前序遍历结果如下:");
binaryTree.preOrder();
// Node:id=1,2,3,5,4
System.out.println();
System.out.println("中序遍历结果如下:");
binaryTree.infixOrder();
// Node:id=2,1,5,3,4
System.out.println();
System.out.println("后序遍历结果如下:");
binaryTree.postOrder();
// Node:id=2,5,4,3,1
// 5.测试[查找]。
// 前序遍历查找的次数 :4
System.out.println();
System.out.println("前序遍历查找结果如下:");
int id = 5;
Node res = binaryTree.preOrderSearch(id);
if (null != res) {
System.out.printf(
"preOrderSearch result: id=[%d], data=[%s]\n",
res.getId(),
res.getData()
);
} else {
System.out.printf("preOrderSearch not found id=[%d] node.\n", id);
}
// 中序遍历查找的次数 :3
System.out.println();
System.out.println("中序遍历查找结果如下:");
Node res1 = binaryTree.infixOrderSearch(id);
if (null != res1) {
System.out.printf(
"infixOrderSearch result: id=[%d], data=[%s]\n",
res1.getId(),
res1.getData()
);
} else {
System.out.printf("infixOrderSearch not found id=[%d] node.\n", id);
}
// 后序遍历查找的次数 :2
System.out.println();
System.out.println("后序遍历查找结果如下:");
Node res2 = binaryTree.postOrderSearch(id);
if (null != res2) {
System.out.printf(
"postOrderSearch result: id=[%d], data=[%s]\n",
res2.getId(),
res2.getData()
);
} else {
System.out.printf("postOrderSearch not found id=[%d] node.\n", id);
}
// 6.测试[删除]。
System.out.println();
System.out.println("删除前,前序遍历结果如下:");
binaryTree.preOrder();
// Node:id=1,2,3,5,4
// 删除id为5的节点。
binaryTree.del(id);
System.out.println("删除后,前序遍历结果如下:");
binaryTree.preOrder();
// Node:id=1,2,3,4
}
}
/**
* 定义二叉树。
*/
class BinaryTree {
/**
* 根节点。
*/
private Node root;
public void setRoot(Node root) {
this.root = root;
}
public void preOrder() {
if (null != this.root) {
this.root.preOrder();
} else {
System.out.println("preOrder error : binary tree is null.");
}
}
public void infixOrder() {
if (null != this.root) {
this.root.infixOrder();
} else {
System.out.println("infixOrder error : binary tree is null.");
}
}
public void postOrder() {
if (null != this.root) {
this.root.postOrder();
} else {
System.out.println("postOrder error : binary tree is null.");
}
}
public Node preOrderSearch(int id) {
if (null != this.root) {
return root.preOrderSearch(id);
} else {
return null;
}
}
public Node infixOrderSearch(int id) {
if (null != this.root) {
return root.infixOrderSearch(id);
} else {
return null;
}
}
public Node postOrderSearch(int id) {
if (null != this.root) {
return this.root.postOrderSearch(id);
} else {
return null;
}
}
public void del(int id) {
if (null != this.root) {
// 若只有一个根节点时,直接进行判断。
if (id == this.root.getId()) {
root = null;
} else {
this.root.del(id);
}
} else {
System.out.println("del node error : binary tree is null.");
}
}
}
/**
* 定义节点。
*/
@Setter
@Getter
class Node {
private int id;
private Object data;
private Node left;
private Node right;
public Node(int id, Object data) {
this.id = id;
this.data = data;
}
@Override
public String toString() {
return "Node:[id=" + this.id + ", data=" + this.data + "]";
}
/**
* 1.前序遍历。
*/
public void preOrder() {
// 输出父节点。
System.out.println(this);
if (null != this.left) {
this.left.preOrder();
}
if (null != this.right) {
this.right.preOrder();
}
}
/**
* 2.中序遍历。
*/
public void infixOrder() {
if (null != this.left) {
this.left.infixOrder();
}
// 输出父节点。
System.out.println(this);
if (null != this.right) {
this.right.infixOrder();
}
}
/**
* 3.后序遍历。
*/
public void postOrder() {
if (null != this.left) {
this.left.postOrder();
}
if (null != this.right) {
this.right.postOrder();
}
// 输出父节点。
System.out.println(this);
}
// -----------分割线-----------
/**
* 1.前序查找。
*
* @param id id
* @return {@link Node}
*/
public Node preOrderSearch(int id) {
if (id == this.getId()) {
return this;
}
Node res = null;
// 向左递归。
if (null != this.left) {
res = this.left.preOrderSearch(id);
}
// 左子树找到。
if (null != res) {
return res;
}
// 向右递归。
if (null != this.right) {
res = this.right.preOrderSearch(id);
}
return res;
}
/**
* 2.中序查找。
*
* @param id id
* @return {@link Node}
*/
public Node infixOrderSearch(int id) {
// 左递归。
Node res = null;
if (null != this.left) {
res = this.left.infixOrderSearch(id);
}
if (null != res) {
return res;
}
// 找到则返回。
if (id == this.getId()) {
return this;
}
// 右递归。
if (null != this.right) {
res = this.right.infixOrderSearch(id);
}
return res;
}
/**
* 3.后序查找。
*
* @param id id
* @return {@link Node}
*/
public Node postOrderSearch(int id) {
Node res = null;
if (null != this.left) {
res = this.left.postOrderSearch(id);
}
if (null != res) {
return res;
}
if (null != this.right) {
res = this.right.postOrderSearch(id);
}
if (null != res) {
return res;
}
if (id == this.getId()) {
return this;
}
return res;
}
// -----------分割线-----------
/**
* 删除节点。
*
*
* 思路分析如下:
* 1. 因为我们的二叉树是[单向]的,所以我们是判断当前节点的子节点是否需要删除节点,而不能去判断当前这个节点是不是需要删除节。
* 2. 如果当前节点的左子节点不为空,并且左子节点就是要删除节点,就将this.left = null; 并且就返回(结束递归删除)。
* 3. 如果当前节点的右子节点不为空,并且右子节点就是要删除节点,就将this.right= null;并且就返回(结束递归删除)。
* 4. 如果第2和第3步没有删除节点,那么我们就需要向左子树进行递归删除。
* 5. 如果第4步也没有删除节点,则应当向右子树进行递归删除。
*
*
* @param id id
*/
public void del(int id) {
if (null != this.left && id == this.left.getId()) {
this.left = null;
return;
}
if (null != this.right && id == this.right.getId()) {
this.right = null;
return;
}
// 向左,向右进行递归删除。
if (null != this.left) {
this.left.del(id);
}
if (null != this.right) {
this.right.del(id);
}
}
}
说明:从数据存储来看,数组存储方式和树的存储方式可以相互转换,即数组可以转换成树,树也可以转换成数组。
示意图:
2 * n + 1
。2 * n + 2
。(n-1) / 2
。需求: 给定一个数组 {1,2,3,4,5,6,7},要求以二叉树前序遍历的方式进行遍历。 前序遍历的结果应当为1,2,4,5,3,6,7。
public class ArrBinaryTreeDemo {
public static void main(String[] args) {
int[] array = {1, 2, 3, 4, 5, 6, 7};
ArrBinaryTree arrBinaryTree = new ArrBinaryTree(array);
arrBinaryTree.preOrder();
// 1,2,4,5,3,6,7
}
}
class ArrBinaryTree {
/**
* 存储数据节点的数组。
*/
private final int[] array;
public ArrBinaryTree(int[] array) {
this.array = array;
}
/**
* 重载 preOrder()。
*/
public void preOrder() {
// 固定从下标 0 开始。
this.preOrder(0);
}
/**
* 前序遍历。
*
* @param index 数组的下标
*/
public void preOrder(int index) {
if (array == null || array.length == 0) {
System.out.println("preOrder error : binary tree is null.");
}
if (null != array) {
// 输出当前这个元素。
System.out.println(array[index]);
// 向左递归遍历。
if ((index * 2 + 1) < array.length) {
preOrder(2 * index + 1);
}
// 向右递归遍历。
if ((index * 2 + 2) < array.length) {
preOrder(2 * index + 2);
}
}
}
}
n+1
(参考公式: 2n-(n-1)=n+1
) 个空指针域。利用二叉链表中的空指针域,存放指向该节点在某种遍历次序下的前驱和后继节点的指针(这种附加的指针称为"线索")。Threaded BinaryTree
),根据线索性质的不同,线索二叉树可分为前序线索二叉树、中序线索二叉树和后序线索二叉树三种。需求:将下面的二叉树,进行中序线索二叉树。中序遍历的数列结果为 {8, 3, 10, 1, 14, 6}。
left 指向的是左子树,也可能是指向的前驱节点。(比如:节点①-left 指向的左子树, 而 节点⑩-left 指向的就是前驱节点。)
right 指向的是右子树,也可能是指向的后继节点。(比如:节点①-right 指向的是右子树,而节点⑩-right 指向的是后继节点。)
代码示例:
public class ThreadedBinaryTreeDemo {
public static void main(String[] args) {
// 准备节点。
Node1 root = new Node1(1, "data1");
Node1 node3 = new Node1(3, "data3");
Node1 node6 = new Node1(6, "data6");
Node1 node8 = new Node1(8, "data8");
Node1 node10 = new Node1(10, "data10");
Node1 node14 = new Node1(14, "data14");
// 手动创建二叉树。
root.setLeft(node3);
root.setRight(node6);
node3.setLeft(node8);
node3.setRight(node10);
node6.setLeft(node14);
// 中序线索化。
ThreadedBinaryTree threadedBinaryTree = new ThreadedBinaryTree();
threadedBinaryTree.setRoot(root);
threadedBinaryTree.threadNodes();
// 以节点⑩来进行测试。
int leftId = node10.getLeft().getId();
int rightId = node10.getRight().getId();
System.out.println("节点⑩的前驱节点id=[" + leftId + "], 后继节点id=[" + rightId + "]");
// 节点⑩的前驱节点id=[3], 后继节点id=[1]
System.out.println();
System.out.println("使用线索化二叉树遍历结果如下:");
threadedBinaryTree.foreach();
// 使用线索化二叉树遍历结果如下:
// Node1:[id=8, data=data8]
// Node1:[id=3, data=data3]
// Node1:[id=10, data=data10]
// Node1:[id=1, data=data1]
// Node1:[id=14, data=data14]
// Node1:[id=6, data=data6]
}
}
/**
* 定义线索二叉树。
*/
class ThreadedBinaryTree {
/**
* 根节点。
*/
private Node1 root;
/**
* 为了实现线索化,需要创建一个指向当前节点的前驱节点指针。
* 递归线索化时,pre总是保留前一个节点。
*/
private Node1 pre = null;
public void setRoot(Node1 root) {
this.root = root;
}
/**
* 中序遍历线索化二叉树。
*/
public void foreach() {
Node1 cur = root;
while (null != cur) {
// 找到线索化后的节点。(第一个找到的是节点⑧)
// 0:表示指向的[左子树]。
while (0 == cur.getLeftType()) {
// 左移。
cur = cur.getLeft();
}
// 输出当前节点。
System.out.println(cur);
// 如果当前节点的右指针指向的是后继节点,就一直输出。
while (1 == cur.getRightType()) {
// 获取后继节点。
cur = cur.getRight();
System.out.println(cur);
}
// 替换遍历节点。
cur = cur.getRight();
}
}
/**
* 重载线索化方法。(便于调用)。
*/
public void threadNodes() {
this.threadNodes(root);
}
/**
* 将树中的节点进行线索化。
*
* @param node 节点
*/
private void threadNodes(Node1 node) {
if (null == node) {
return;
}
// 1.先线索化[左子树]。
threadNodes(node.getLeft());
// 2.线索化[当前节点]。
// 2.1 先处理当前节点的[前驱节点] --- (此处以节点⑧为例,他的left=null,则leftType=1)。
if (null == node.getLeft()) {
// 左指针指向前驱节点,并修改左指针类型,指向前驱节点。
node.setLeft(pre);
node.setLeftType(1);
}
// 2.2 再处理[后继节点]。
if (null != pre && null == pre.getRight()) {
pre.setRight(node);
pre.setRightType(1);
}
// 处理完每一个节点后,让当前节点成为下一个节点的前驱节点。
pre = node;
// 3.线索化[右子树]。
threadNodes(node.getRight());
}
}
/**
* 定义节点。
*/
@Setter
@Getter
class Node1 {
private int id;
private Object data;
private Node1 left;
private Node1 right;
/**
* 0 表示指向的[左子树]。
* 1 表示指向[前驱节点]。
*/
private int leftType;
/**
* 0 表示指向的[右子树]。
* 1 表示指向[后继节点]。
*/
private int rightType;
public Node1(int id, Object data) {
this.id = id;
this.data = data;
}
@Override
public String toString() {
return "Node1:[id=" + this.id + ", data=" + this.data + "]";
}
}
“-------怕什么真理无穷,进一寸有一寸的欢喜。”
微信公众号搜索:饺子泡牛奶。