[toc]

数据结构之顺序储存二叉树

1. 顺序储存二叉树

1.1 顺序存储二叉树的概念

  • 从数据存储来看,数组存储方式和树的存储方式可以相互转换,即 数组可以转换成树, 树也可以转换成数组。

image-20200314171624898

1.2 顺序存储二叉树的特点

  1. 顺序二叉树通常只考虑完全二叉树
  2. 第 n 个元素的左子节点为 2 * n + 1
  3. 第 n 个元素的右子节点为 2 * n + 2
  4. 第 n 个元素的父节点为 (n-1) / 2
  5. n : 表示二叉树中的第几个元素(按 0 开始编号如图所示)

1.3 顺序存储二叉树遍历

  • 要求在遍历数组 arr 时,仍然可以以前序遍历,中序遍历和后序遍历的方式完成结点的遍历
  • 给你一个数组 {1,2,3,4,5,6,7},要求以二叉树前序遍历,中序遍历和后序遍历的方式进行遍历。
    • 前序遍历的结果应当为1,2,4,5,3,6,7
    • 中序遍历的结果应当为4 2 5 1 6 3 7
    • 后序遍历的结果应当为4 5 2 6 7 3 1
1.3.1 代码实现
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
public class ArrBinaryTreeDemo {

public static void main(String[] args) {

//测试
int[] arr = {1,2,3,4,5,6,7};

//创建一个ArrBinaryTree
ArrBinaryTree arrBinaryTree = new ArrBinaryTree(arr);
System.out.println("顺序储存的二叉树的前序遍历:");
arrBinaryTree.preOrder();//1 2 4 5 3 6 7

System.out.println("顺序储存的二叉树的中序遍历:");
arrBinaryTree.infixOrder();// 4 2 5 1 6 3 7

System.out.println("顺序储存的二叉树的后序遍历:");
arrBinaryTree.postOrder();// 4 5 2 6 7 3 1
}

}

/**
* 编写一个ArrBinaryTree实现顺序储存二叉树遍历
*
* @author DuanChaojie
* @date 2020年3月14日 下午1:28:28
* @version 1.0
*/
class ArrBinaryTree {

private int[] arr;// 储存数据节点的数组

public ArrBinaryTree(int[] arr) {
this.arr = arr;
}

//方法的重载
public void preOrder() {
this.preOrder(0);
}

public void infixOrder() {
this.infixOrder(0);
}

public void postOrder() {
this.postOrder(0);
}



/**
* 顺序储存数组的前序遍历
* @param index 数组的下标
*/
public void preOrder(int index) {
// 如果数组为空,或者arr.length = 0
if (arr == null || arr.length == 0) {
System.out.println("数组为空,不能按照二叉树的前序遍历");
}

// 输出当前这个元素
System.out.println(arr[index]);

// 向左递归遍历
if ((index * 2 + 1) < arr.length) {
preOrder((index * 2 + 1));
}

// 向右递归遍历
if ((index * 2 + 2) < arr.length) {
preOrder((index * 2 + 2));
}

}


/**
* 顺序储存数组的中序遍历
* @param index 数组的下标
*/
public void infixOrder(int index) {
// 如果数组为空,或者arr.length = 0
if (arr == null || arr.length == 0) {
System.out.println("数组为空,不能按照二叉树的前序遍历");
}


// 向左递归遍历
if ((index * 2 + 1) < arr.length) {
infixOrder((index * 2 + 1));
}

// 输出当前这个元素
System.out.println(arr[index]);


// 向右递归遍历
if ((index * 2 + 2) < arr.length) {
infixOrder((index * 2 + 2));
}

}


/**
* 顺序储存数组的后序遍历
* @param index 数组的下标
*/
public void postOrder(int index) {
// 如果数组为空,或者arr.length = 0
if (arr == null || arr.length == 0) {
System.out.println("数组为空,不能按照二叉树的前序遍历");
}


// 向左递归遍历
if ((index * 2 + 1) < arr.length) {
postOrder((index * 2 + 1));
}

// 向右递归遍历
if ((index * 2 + 2) < arr.length) {
postOrder((index * 2 + 2));
}

// 输出当前这个元素
System.out.println(arr[index]);


}


}

2. 线索化二叉树

  • n 个结点的二叉链表中含有 n+1 【公式 2n-(n-1)=n+1】 个空指针域。利用二叉链表中的空指针域,存放指向该结点在某种遍历次序下的前驱和后继结点的指针(这种附加的指针称为”线索”)
  • 这种加上了线索的二叉链表称为线索链表,相应的二叉树称为线索二叉树(Threaded BinaryTree)。根据线索性质的不同,线索二叉树可分为前序线索二叉树、中序线索二叉树和后序线索二叉树三种
  • 一个结点的前一个结点,称为 前驱结点
  • 一个结点的后一个结点,称为后继结点

2.1 线索二叉树应用案例

将下面的二叉树,进行中序线索二叉树。中序遍历的数列为 {8, 3, 10, 1, 14, 6}

image-20200314172600780

当线索化二叉树后,Node 节点的 属性 left 和 right ,有如下情况:

  1. left 指向的是左子树,也可能是指向的前驱节点.
    1. ① 节点 left 指向的左子树,
    2. ⑩ 节点的 left 指向De就是前驱节点
  2. right 指向的是右子树,也可能是指向后继节点。
    1. ① 节点 right 指向的是右子树
    2. ⑩ 节点的 right 指向的是后继节点

2.2 代码实现

2.2.1 HeroNode 部分
  1. 如果 leftType == 0 表示指向的是左子树,如果是1 表示指向的是前驱节点
  2. 如果 rightType == 0 表示指向是右子树, 如果 1 表示指向后继结点
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
/**
*创建HeroNode节点
* */
class HeroNode{

private int no;

private String name;

private HeroNode left;//默认为null

private HeroNode right;//默认为null


//1.如果leftType == 0 表示指向的是左子树,如果是1 表示指向的是前驱节点
//2. 如果 rightType == 0 表示指向是右子树, 如果 1 表示指向后继结点
private int leftType;

private int rightType;


//构造器
public HeroNode(int no, String name) {
super();
this.no = no;
this.name = name;
}

//get和set方法
//省略....



//重写toString方法
@Override
public String toString() {
return "HeroNode [no=" + no + ", name=" + name + "]";
}

//.......

}
2.2.2 ThreadedBinaryTree 部分

public void threadedList();遍历线索化二叉树的方法

public void threadedNode();

public void threadedNode(HeroNode node);

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
/**
* 定义ThreadedBinaryTree 实现了线索化功能的二叉树
* @author Administrator
* @date 2020年3月14日 下午1:21:01
* @version 1.0
*/
class ThreadedBinaryTree{

private HeroNode root;

private HeroNode pre = null;
/*
* 为了实现线索化,需要创建要给指向当前节点的前驱节点的指针
* 在递归进行线索化时,pre 总是保留前一个节点
* */


//get和set方法
public HeroNode getRoot() {
return root;
}

public void setRoot(HeroNode root) {
this.root = root;
}

public HeroNode getPre() {
return pre;
}

public void setPre(HeroNode pre) {
this.pre = pre;
}


/**
*遍历线索化二叉树的方法
*/
public void threadedList() {
//定义一个变量,存储当前遍历的节点,从root开始
HeroNode node = root;
while(node != null) {
//循环找到leftType == 1的节点,从root开始
//后面随着遍历的变化,因为当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();
}
}




//重载的方法
public void threadedNode() {
this.threadedNode(root);
}

/**
*
* @param node 当前需要线索化的节点
*/
public void threadedNode(HeroNode node) {
//如果node == null 不能线索化
if(node == null) {
return;
}

//先线索化左子树
threadedNode(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;

//线索化右子树
threadedNode(node.getRight());
}

//.....

}
2.2.3 测试
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
public class ThreadedBinaryTreeDemo {

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.threadedNode();

//测试: 以 10 号节点测试
HeroNode leftNode = node5.getLeft();
HeroNode rightNode = node5.getRight();
System.out.println("10 号结点的前驱结点是 =" + leftNode); //3
System.out.println("10 号结点的后继结点是=" + rightNode); //1
//当线索化二叉树后,能在使用原来的遍历方法
//threadedBinaryTree.infixOrder();
System.out.println("使用线索化的方式遍历 线索化二叉树");
threadedBinaryTree.threadedList(); // 8, 3, 10, 1, 14, 6

}
}