数据结构(二)之二叉树
基础概念
二叉树(binary tree)是一棵树,其中每个结点都不能有多于两个儿子。
二叉排序树或者是一棵空树,或者是具有下列性质的二叉树:
(1)若左子树不空,则左子树上所有结点的值均小于或等于它的根结点的值;
(2)若右子树不空,则右子树上所有结点的值均大于或等于它的根结点的值;
(3)左、右子树也分别为二叉排序树;
二叉树的遍历
二叉树的遍历是指从根节点出发,按照某种次序依次访问二叉树中所有结点,使得每个结点被访问一次且仅被访问一次。二叉树的遍历方式有很多,主要有前序遍历,中序遍历,后序遍历。
前序遍历
前序遍历的规则是:若二叉树为空,则空操作返回,否则先访问根节点,然后前序遍历左子树,再前序遍历右子树
中序遍历
中序遍历的规则是:若树为空,则空操作返回;否则从根节点开始(注意并不是先访问根节点),中序遍历根节点的左子树,然后是访问根节点,最后中序遍历右子树。可以看到,如果是二叉排序树,中序遍历的结果就是个有序序列。
后序遍历
后序遍历的规则是:若树为空,则空操作返回;然后先遍历左子树,再遍历右子树,最后访问根结点,在遍历左、右子树时,仍然先遍历左子树,然后遍历右子树,最后遍历根结点。
删除结点
对于二叉排序树的其他操作,比如插入,遍历等,比较容易理解;而删除操作相对复杂些。对于要删除的结点,有以下三种情况:
1.叶子结点;
2.仅有左子树或右子树的结点;
3.左右子树都有结点;
对于1(要删除结点为叶子结点)直接删除,即直接解除父节点的引用即可,对于第2种情况(要删除的结点仅有一个儿子),只需用子结点替换掉父节点即可;而对于要删除的结点有两个儿子的情况,比较常用处理逻辑为,在其子树中找寻一个结点来替换,而这个结点我们成为中序后继结点。
可以看到,我们找到的这个用来替换的结点,可以是删除结点的右子树的最小结点(6),也可以是其左子树的最大结点(4),这样可以保证替换后树的整体结构不用发生变化。为什么称为中序后继结点呢?我们来看下这棵树的中序遍历结果 1-2-3-4-5-6-7-8-9。可以很清晰的看到,其实要找的这个结点,可以是结点5的前驱或者后继。
代码实现
1 package treeDemo;
2
3 /**
4 * Created by chengxiao on 2017/02/12.
5 */
6 public class BinaryTree {
7 //根节点
8 private Node root;
9 /**
10 * 树的结点
11 */
12 private static class Node{
13 //数据域
14 private long data;
15 //左子结点
16 private Node leftChild;
17 //右子结点
18 private Node rightChild;
19 Node(long data){
20 this.data = data;
21 }
22 }
23
24 /**
25 * 插入结点
26 * @param data
27 */
28 public void insert(long data){
29 Node newNode = new Node(data);
30 Node currNode = root;
31 Node parentNode;
32 //如果是空树
33 if(root == null){
34 root = newNode;
35 return;
36 }
37 while(true){
38 parentNode = currNode;
39 //向右搜寻
40 if(data > currNode.data){
41 currNode = currNode.rightChild;
42 if(currNode == null){
43 parentNode.rightChild = newNode;
44 return;
45 }
46 }else{
47 //向左搜寻
48 currNode = currNode.leftChild;
49 if(currNode == null){
50 parentNode.leftChild = newNode;
51 return;
52 }
53 }
54 }
55
56 }
57
58 /**
59 * 前序遍历
60 * @param currNode
61 */
62 public void preOrder(Node currNode){
63 if(currNode == null){
64 return;
65 }
66 System.out.print(currNode.data+" ");
67 preOrder(currNode.leftChild);
68 preOrder(currNode.rightChild);
69 }
70
71 /**
72 * 中序遍历
73 * @param currNode
74 */
75 public void inOrder(Node currNode){
76 if(currNode == null){
77 return;
78 }
79 inOrder(currNode.leftChild);
80 System.out.print(currNode.data+" ");
81 inOrder(currNode.rightChild);
82
83 }
84
85 /**
86 * 后序遍历
87 * @param currNode
88 */
89 public void postOrder(Node currNode){
90 if(currNode == null){
91 return;
92 }
93 postOrder(currNode.leftChild);
94 postOrder(currNode.rightChild);
95 System.out.print(currNode.data+" ");
96 }
97
98 /**
99 * 查找结点
100 * @param data
101 * @return
102 */
103 public Node find(long data){
104 Node currNode = root;
105 while(currNode!=null){
106 if(data>currNode.data){
107 currNode = currNode.rightChild;
108 }else if(data<currNode.data){
109 currNode = currNode.leftChild;
110 }else{
111 return currNode;
112 }
113 }
114 return null;
115 }
116
117 /**
118 * 删除结点 分为3种情况
119 * 1.叶子结点
120 * 2.该节点有一个子节点
121 * 3.该节点有二个子节点
122 * @param data
123 */
124 public boolean delete(long data) throws Exception {
125 Node curr = root;
126 //保持一个父节点的引用
127 Node parent = curr;
128 //删除结点是左子结点还是右子结点,
129 boolean isLeft = true;
130 while(curr != null && curr.data!=data){
131 parent = curr;
132 if(data > curr.data){
133 curr = curr.rightChild;
134 isLeft = false;
135 }else{
136 curr = curr.leftChild;
137 isLeft = true;
138 }
139 }
140 if(curr==null){
141 throw new Exception("要删除的结点不存在");
142 }
143 //第一种情况,要删除的结点为叶子结点
144 if(curr.leftChild == null && curr.rightChild == null){
145 if(curr == root){
146 root = null;
147 return true;
148 }
149 if(isLeft){
150 parent.leftChild = null;
151 }else{
152 parent.rightChild = null;
153 }
154 }else if(curr.leftChild == null){
155 //第二种情况,要删除的结点有一个子节点且是右子结点
156 if(curr == root){
157 root = curr.rightChild;
158 return true;
159 }
160 if(isLeft){
161 parent.leftChild = curr.rightChild;
162 }else{
163 parent.rightChild = curr.rightChild;
164 }
165 }else if(curr.rightChild == null){
166 //第二种情况,要删除的结点有一个子节点且是左子结点
167 if(curr == root){
168 root = curr.leftChild;
169 return true;
170 }
171 if(isLeft){
172 parent.leftChild = curr.leftChild;
173 }else{
174 parent.rightChild = curr.leftChild;
175 }
176 }else{
177 //第三种情况,也是最复杂的一种情况,要删除的结点有两个子节点,需要找寻中序后继结点
178 Node succeeder = getSucceeder(curr);
179 if(curr == root){
180 root = succeeder;
181 return true;
182 }
183 if(isLeft){
184 parent.leftChild = succeeder;
185 }else{
186 parent.rightChild = succeeder;
187 }
188 //当后继结点为删除结点的右子结点
189 succeeder.leftChild = curr.leftChild;
190
191 }
192 return true;
193 }
194 public Node getSucceeder(Node delNode){
195 Node succeeder = delNode;
196 Node parent = delNode;
197 Node currNode = delNode.rightChild;
198 //寻找后继结点
199 while(currNode != null){
200 parent = succeeder;
201 succeeder = currNode;
202 currNode = currNode.leftChild;
203 }
204 //如果后继结点不是要删除结点的右子结点
205 if(succeeder != delNode.rightChild){
206 parent.leftChild = succeeder.rightChild;
207 //将后继结点的左右子结点分别指向要删除结点的左右子节点
208 succeeder.leftChild = delNode.leftChild;
209 succeeder.rightChild = delNode.rightChild;
210 }
211 return succeeder;
212
213 }
214 public static void main(String []args) throws Exception {
215 BinaryTree binaryTree = new BinaryTree();
216 //插入操作
217 binaryTree.insert(5);
218 binaryTree.insert(2);
219 binaryTree.insert(8);
220 binaryTree.insert(1);
221 binaryTree.insert(3);
222 binaryTree.insert(6);
223 binaryTree.insert(10);
224 //前序遍历
225 System.out.println("前序遍历:");
226 binaryTree.preOrder(binaryTree.root);
227 System.out.println();
228 //中序遍历
229 System.out.println("中序遍历:");
230 binaryTree.inOrder(binaryTree.root);
231 System.out.println();
232 //后序遍历
233 System.out.println("后序遍历:");
234 binaryTree.postOrder(binaryTree.root);
235 System.out.println();
236 //查找结点
237 Node node = binaryTree.find(10);
238 System.out.println("找到结点,其值为:"+node.data);
239 //删除结点
240 binaryTree.delete(8);
241 System.out.print("删除结点8,中序遍历:");
242 binaryTree.preOrder(binaryTree.root);
243 }
244 }
执行结果
前序遍历:
5 2 1 3 8 6 10
中序遍历:
1 2 3 5 6 8 10
后序遍历:
1 3 2 6 10 8 5
找到结点,其值为:10
删除结点8,中序遍历:5 2 1 3 10 6
作者: dreamcatcher-cx
出处: <http://www.cnblogs.com/chengxiao/>
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在页面明显位置给出原文链接。