启迪思维:链式树

最近公司项目比较忙,又加上要给十几个新人做培训,晚上和周末又要个姐姐装修店铺(太痛苦的事情,而且装修的效果也不是很好),前些天天涯上有一篇寒门难出贵子帖子很火,那个作者语文水平和我一个级别的(错别字太多了),大家可以看淘宝鬼脚七整理版本(关注他的微信公众号),虽然里边有些观点不是很认同,但对我影响很大,出生寒门的兄弟一定去静下心来好读读,这个远比我下面写的这边技术文章带给你更多思考,非常欢迎大家留言讨论文章的观点。

前面的文章一直在分析数据结构中线性结构部分,从本篇文章以后将进入非线性结构部分,前面未弄懂部分可以在网上找些视频结合我写的文章在补补,只有这样后面学习起来才相对容易些,下面进入正题,首先还是先分享一个软件带来误会故事。

以前学校旁边有一个火车票代售点,每次到过年学生放假的时候,偶尔会看有人因为先买没有买到票,而后面人确买到票和售票员吵架,估计每次发生这样事情,售票员自己也解释不清楚,连当时学计算机的我也猜测后面人给售票员好处,后来偶然看到一篇帖子才明白,原来火车票售票系统,会把一定数量的票锁定给一个售票厅,前一个人买票时候,余票被其他售票厅锁住了,后面去人再买票的时候正好赶上其他售票厅释放余票(太幸运),现在技术和硬件越来越好,从09年后我就没有见过为这事吵架的。

 

一:概念

树状图是一种数据结构,它是由nn>=1)个有限结点组成一个具有层次关系的集合。把它叫做“树”是因为它看起来像一棵倒挂的树,也就是说它是根朝上,而叶朝下的。它具有以下的特点:

1每个结点有零个或多个子结点;

2没有前驱的结点称为根结点;

3每一个非根结点有且只有一个父结点;

4除了根结点外,每个子结点可以分为m个不相交的子树;

 

二:名称解释

子孙:以某节点为根的子树中任一节点都称为该节点的子孙;

森林:由mm>=0)棵互不相交的树的集合称为森林;

树的度:一棵树中,最大的节点的度称为树的度;

节点的度:一个节点含有的子树的个数称为该节点的度;

兄弟节点:具有相同父节点的节点互称为兄弟节点;

堂兄弟节点:双亲在同一层的节点互为堂兄弟;

节点的祖先:从根到该节点所经分支上的所有节点;

节点的层次:从根开始定义起,根为第1层,根的子节点为第2层,以此类推;

树的高度或深度:树中节点的最大层次;

双亲节点或父节点:若一个结点含有子节点,则这个节点称为其子节点的父节点;

孩子节点或子节点:一个节点含有的子树的根节点称为该节点的子节点;

叶节点或终端节点:度为零的节点;

非终端节点或分支节点:度不为零的节点;

 

三:示例图

 

 

四:树的应用

1、查找算法中应用(数据库索引-B-tree)

2、哈夫曼编码

3、最短路径问题

4、几乎所有的编译器都需要实现一个表达式树

上面这些树的应用,面试中也会经常问到,争取后面能写比较好分析它们的文章

五:树的分类

上面的截图是维基百科里边的解释,画红框的在后面的文章会分析到;

 

五:代码分析

1、创建一颗树

 1 /**
 2  *根据文件内容递归创建树
 3  */
 4 void CreateTreeFromFile(ifstream &f) {
 5     //每次读取一个字符
 6     T e;
 7     InputFromFile(f, e);
 8     if (e == '#') {
 9         return;
10     }
11 
12     //创建一个新的节点
13     root = new TNode<T> (e);
14 
15     //创建左右两个树对象
16     LinkTree<T> left, right;
17     //递归创建左子树
18     left.CreateTreeFromFile(f);
19     //递归创建右子树
20     right.CreateTreeFromFile(f);
21 
22     //设置根节点的左孩子接左子树的根节点
23     root->lchild = left.root;
24     left.root = 0;
25     //设置根节点的右孩子接右子树的根节点
26     root->rchild = right.root;
27     right.root = 0;
28 }
29 /**
30  *读取文件并赋值给遍历c
31  */
32 void InputFromFile(ifstream &f, T &c) {
33     f >> c;
34 }

2、清空一颗树

 1 /**
 2  *清空树的所有节点
 3  */
 4 void Clear() {
 5     Clear(root);
 6 }
 7 /**
 8  *根据节点递归清空树
 9  */
10 void Clear(TNode<T>* t) {
11     //判断指针是否为空
12     if (t) {
13         //获取资源立即放入管理对象(参考Effective C++里边条款13)
14         //tr1::shared_ptr(引用计数智慧指针)管理对象比auto_ptr更强大
15         std::auto_ptr<TNode<T> > new_ptr(t);
16 
17         //递归清空右子树
18         Clear(new_ptr->rchild);
19 
20         //递归清空左子树
21         Clear(new_ptr->lchild);
22     }
23 
24     //清空树的根节点
25     t = 0;
26 }

3、判断树是否为空

1 /**
2  * 若树为空,则返回true;否则返回false
3  */
4 bool IsEmpty() {
5     return root == 0;
6 }

4、计算树的深度

 1 /**
 2  * 以传入节点为基础计算树的深度
 3  */
 4 int GetTreeDept(const TNode<T> *t) {
 5     int i, j;
 6     if (t == 0) {
 7         return 0;
 8     } else {
 9         //递归计算左子树的深度
10         i = this->GetTreeDept(t->lchild);
11         //递归计算右子树的深度
12         j = this->GetTreeDept(t->rchild);
13     }
14 
15     //t的深度为其左右子树中深度中的大者加1
16     return i > j ? i + 1 : j + 1;
17 }

5、前序非递归遍历树 

如下动画flash不知道是网上那位大牛做,在这里借用下,阅读文章的朋友如果有做flash做的比较好,请帮忙指点下我,谢谢

如下中图详细分析下面代码运行过程

 

如下代码

 1 /**
 2  *前序非递归(利用栈)遍历二叉树
 3  *前序遍历的规则:根左右
 4  *非递归遍历树会经常当做面试题,考察面试者的编程能力
 5  *防止下次被鄙视,应该深入理解并且动手在纸写出来
 6  */
 7 void PreOrderTraverse() {
 8     //申明一个栈对象
 9     stack<TNode<T>*> s;
10     //t首先指向根节点
11     TNode<T> *t = root;
12     //压入一个空指针,作为判断条件
13     s.push(0);
14 
15     //如果t所值节点非空
16     while (t != 0) {
17         //直接访问根节点
18         std::cout << (&t->data) << " ";
19 
20         //右孩子指针为非空
21         if (t->rchild != 0) {
22             //入栈右孩子指针
23             s.push(t->rchild);
24         }
25 
26         //左孩子指针为非空
27         if (t->lchild != 0) {
28             //直接指向其左孩子
29             t = t->lchild;
30         } else {//左孩子指针为空
31             //获取栈顶元素(右孩子指针)
32             t = s.top();
33             //清楚栈顶元素
34             s.pop();
35         }
36 
37     }
38 }

6、中序非递归遍历树

如下动画flash不知道是网上那位大牛做,在这里借用下,阅读文章的朋友如果有做flash做的比较好,请帮忙指点下我,谢谢 

如下图是代码遍历树的节点顺序,看的不是很明白的朋友,可以像前面先序中那样方式,用图画出每一个步骤,便于跟好的理解

 

如下是代码

 1 /**
 2  *中序非递归(利用栈)遍历二叉树
 3  *前序遍历的规则:左根右
 4  */
 5 void InOrderTraverse() {
 6     //申明一个栈对象
 7     stack<TNode<T>*> s;
 8     //t首先指向根节点
 9     TNode<T>* t = root;
10 
11     //节点不为空或者栈对象不为空,都进入循环
12     while (t != 0 || !s.empty()) {
13         //如果t节点非空
14         if (t) {
15             //入栈t节点
16             s.push(t);
17             //t节点指向其左孩子
18             t = t->lchild;
19         } else {
20             //获取栈顶元素(左孩子指针)
21             t = s.top();
22             //清楚栈顶元素
23             s.pop();
24             //直接访问t节点
25             std::cout << (&t->data) << " ";
26             //t节点指向其右孩子
27             t = t->rchild;
28         }
29     }
30 }

7、后序非递归遍历树

如下动画flash不知道是网上那位大牛做,在这里借用下,阅读文章的朋友如果有做flash做的比较好,请帮忙指点下我,谢谢  

如下图是代码遍历树的节点顺序,看的不是很明白的朋友,可以像前面先序中那样方式,用图画出每一个步骤,便于跟好的理解

如下是代码

 1 /**
 2  *后序非递归(利用栈)遍历二叉树
 3  *前序遍历的规则:左右根
 4  */
 5 void PostOrderTraverse() {
 6     //申明一个栈对象
 7     stack<TNode<T>*> s;
 8     //t首先指向根节点
 9     TNode<T>* t = root;
10     //申请中间变量,用做判断标识
11     TNode<T>* r;
12     //节点不为空或者栈对象不为空,都进入循环
13     while (t != 0 || !s.empty()) {
14         //如果t节点非空
15         if (t) {
16             //入栈t节点
17             s.push(t);
18             //t节点指向其左孩子
19             t = t->lchild;
20         } else {
21             //获取栈顶元素(左孩子指针)
22             t = s.top();
23             //判断t的右子树是否存在并且没有访问过
24             if (t->rchild && t->rchild != r) {
25                 //t节点指向其右孩子
26                 t = t->rchild;
27                 //入栈t节点
28                 s.push(t);
29                 //t节点指向其左孩子
30                 t = t->lchild;
31             } else {
32                 //获取栈顶元素(左孩子指针)
33                 t = s.top();
34                 //清楚栈顶元素
35                 s.pop();
36                 //直接访问t节点
37                 std::cout << (&t->data) << " ";
38                 //设置已经访问过的节点,防止多次访问(右孩子指针)
39                 r = t;
40                 t = 0;
41             }
42         }
43     }
44 }

8、根据模式递归遍历二叉树

 1 /**
 2  * 根据模式递归遍历二叉树
 3  * 下面代码相对比较简单,只要记住遍历树的规则并且弄一点递归,都可以写出来
 4  * 如果在面试中实在写不出来非递归方式,可以写一个递归版本,也许可以争取一个好的工作
 5  */
 6 void OrderTraverse(const TNode<T>* t, Style mode) {
 7     if (t) {
 8         //先序遍历二叉树:根左右
 9         if (mode == Pre) {
10             //直接访问t节点
11             std::cout << (&t->data) << " ";
12             //递归遍历左子树
13             this->OrderTraverse(t->lchild, mode);
14             //递归遍历右子树
15             this->OrderTraverse(t->rchild, mode);
16         }
17         //中序遍历二叉树:左根右
18         if (mode == In) {
19             //递归遍历左子树
20             this->OrderTraverse(t->lchild, mode);
21             //直接访问t节点
22             std::cout << (&t->data) << " ";
23             //递归遍历右子树
24             this->OrderTraverse(t->rchild, mode);
25         }
26         //后序遍历二叉树:左右根
27         if (mode == Post) {
28             //递归遍历左子树
29             this->OrderTraverse(t->lchild, mode);
30             //递归遍历右子树
31             this->OrderTraverse(t->rchild, mode);
32             //直接访问t节点
33             std::cout << (&t->data) << " ";
34         }
35     }
36 }

9、运行结果,由于在虚拟机中打中文,实在太痛苦,弄一点英文装下B

 测试代码如下:

 1 void test() {
 2 
 3     ifstream fin("data.txt");
 4     this->CreateTreeFromFile(fin);
 5     std::cout << "create tree success" << std::endl;
 6 
 7     std::cout << "create tree after is null ? ";
 8     std::cout << boolalpha << this->IsEmpty();
 9 
10     std::cout << std::endl;
11     std::cout << "calculated depth of the tree begins" << std::endl;
12     std::cout << "tree is dept = " << this->GetTreeDept(this->GetRoot());
13     std::cout << std::endl;
14     std::cout << "calculated depth of the tree end" << std::endl;
15 
16     std::cout << std::endl;
17     std::cout << "not recursion--------------------begin" << std::endl;
18     std::cout << "pre order traverse: ";
19     this->PreOrderTraverse();
20     std::cout << std::endl;
21 
22     std::cout << "in order traverse: ";
23     this->InOrderTraverse();
24     std::cout << std::endl;
25 
26     std::cout << "post order traverse: ";
27     this->PostOrderTraverse();
28     std::cout << std::endl;
29     std::cout << "not recursion--------------------end" << std::endl;
30 
31     std::cout << std::endl;
32     std::cout << "recursion--------------------begin" << std::endl;
33     std::cout << "pre order traverse:";
34     this->OrderTraverse(this->GetRoot(), Pre);
35     std::cout << std::endl;
36 
37     std::cout << "in order traverse:";
38     this->OrderTraverse(this->GetRoot(), In);
39     std::cout << std::endl;
40 
41     std::cout << "post order traverse:";
42     this->OrderTraverse(this->GetRoot(), Post);
43     std::cout << std::endl;
44     std::cout << "recursion--------------------end" << std::endl;
45 }
View Code

10、完整代码

TNode.h 

 1 /*
 2  * TLNode.h
 3  *
 4  *  Created on: 2013-7-6
 5  *      Author: sunysen
 6  */
 7 
 8 #ifndef TLNODE_H_
 9 #define TLNODE_H_
10 template <class T>
11 class TNode{
12 public:
13     T data;
14     TNode *rchild,*lchild;
15     TNode(T value):data(value),rchild(0),lchild(0){}
16 };
17 
18 #endif /* TLNODE_H_ */
View Code

  LinkTree.h

  1 /*
  2  * LinkTree.h
  3  *  Created on: 2013-7-6
  4  *      Author: sunysen
  5  */
  6 
  7 #ifndef LINKTREE_H_
  8 #define LINKTREE_H_
  9 #include "core/common/Common.h"
 10 #include "core/node/TNode.h"
 11 #include <queue>
 12 #include <stack>
 13 
 14 //树的三种遍历方式
 15 enum Style {
 16     Pre, In, Post
 17 };
 18 
 19 /**
 20  *链式树
 21  */
 22 template<class T>
 23 class LinkTree {
 24 private:
 25     TNode<T> *root;//树的根节点
 26 
 27     /**
 28      *读取文件并赋值给遍历c
 29      */
 30     void InputFromFile(ifstream &f, T &c) {
 31         f >> c;
 32     }
 33 
 34     /**
 35      *根据节点递归清空树
 36      */
 37     void Clear(TNode<T>* t) {
 38         //判断指针是否为空
 39         if (t) {
 40             //获取资源立即放入管理对象(参考Effective C++里边条款13)
 41             //tr1::shared_ptr(引用计数智慧指针)管理对象比auto_ptr更强大
 42             std::auto_ptr<TNode<T> > new_ptr(t);
 43 
 44             //递归清空右子树
 45             Clear(new_ptr->rchild);
 46 
 47             //递归清空左子树
 48             Clear(new_ptr->lchild);
 49         }
 50 
 51         //清空树的根节点
 52         t = 0;
 53     }
 54 public:
 55     /**
 56      * 构造函数初始化树根节点
 57      */
 58     LinkTree() :
 59         root(0) {
 60     }
 61     /**
 62      *析构行数释放所有构造的资源
 63      */
 64     ~LinkTree() {
 65         Clear();
 66     }
 67 
 68     /**
 69      *根据文件内容递归创建树
 70      */
 71     void CreateTreeFromFile(ifstream &f) {
 72         //每次读取一个字符
 73         T e;
 74         InputFromFile(f, e);
 75         if (e == '#') {
 76             return;
 77         }
 78 
 79         //创建一个新的节点
 80         root = new TNode<T> (e);
 81 
 82         //创建左右两个树对象
 83         LinkTree<T> left, right;
 84         //递归创建左子树
 85         left.CreateTreeFromFile(f);
 86         //递归创建右子树
 87         right.CreateTreeFromFile(f);
 88 
 89         //设置根节点的左孩子接左子树的根节点
 90         root->lchild = left.root;
 91         left.root = 0;
 92         //设置根节点的右孩子接右子树的根节点
 93         root->rchild = right.root;
 94         right.root = 0;
 95     }
 96 
 97     /**
 98      * 若树为空,则返回true;否则返回false
 99      */
100     bool IsEmpty() {
101         return root == 0;
102     }
103     /**
104      *清空树的所有节点
105      */
106     void Clear() {
107         Clear(root);
108     }
109 
110     /**
111      *获取const修饰的根节点,相当于做一次类型转换
112      */
113     const TNode<T>* GetRoot() {
114         return root;
115     }
116 
117     /**
118      * 以传入节点为基础计算树的深度
119      */
120     int GetTreeDept(const TNode<T> *t) {
121         int i, j;
122         if (t == 0) {
123             return 0;
124         } else {
125             //递归计算左子树的深度
126             i = this->GetTreeDept(t->lchild);
127             //递归计算右子树的深度
128             j = this->GetTreeDept(t->rchild);
129         }
130 
131         //t的深度为其左右子树中深度中的大者加1
132         return i > j ? i + 1 : j + 1;
133     }
134 
135 
136     /**
137      *前序非递归(利用栈)遍历二叉树
138      *前序遍历的规则:根左右
139      *非递归遍历树会经常当做面试题,考察面试者的编程能力
140      *防止下次被鄙视,应该深入理解并且动手在纸写出来
141      */
142     void PreOrderTraverse() {
143         //申明一个栈对象
144         stack<TNode<T>*> s;
145         //t首先指向根节点
146         TNode<T> *t = root;
147         //压入一个空指针,作为判断条件
148         s.push(0);
149 
150         //如果t所值节点非空
151         while (t != 0) {
152             //直接访问根节点
153             std::cout << (&t->data) << " ";
154 
155             //右孩子指针为非空
156             if (t->rchild != 0) {
157                 //入栈右孩子指针
158                 s.push(t->rchild);
159             }
160 
161             //左孩子指针为非空
162             if (t->lchild != 0) {
163                 //直接指向其左孩子
164                 t = t->lchild;
165             } else {//左孩子指针为空
166                 //获取栈顶元素(右孩子指针)
167                 t = s.top();
168                 //清楚栈顶元素
169                 s.pop();
170             }
171 
172         }
173     }
174 
175     /**
176      *中序非递归(利用栈)遍历二叉树
177      *前序遍历的规则:左根右
178      */
179     void InOrderTraverse() {
180         //申明一个栈对象
181         stack<TNode<T>*> s;
182         //t首先指向根节点
183         TNode<T>* t = root;
184 
185         //节点不为空或者栈对象不为空,都进入循环
186         while (t != 0 || !s.empty()) {
187             //如果t节点非空
188             if (t) {
189                 //入栈t节点
190                 s.push(t);
191                 //t节点指向其左孩子
192                 t = t->lchild;
193             } else {
194                 //获取栈顶元素(左孩子指针)
195                 t = s.top();
196                 //清楚栈顶元素
197                 s.pop();
198                 //直接访问t节点
199                 std::cout << (&t->data) << " ";
200                 //t节点指向其右孩子
201                 t = t->rchild;
202             }
203         }
204     }
205     /**
206      *后序非递归(利用栈)遍历二叉树
207      *前序遍历的规则:左右根
208      */
209     void PostOrderTraverse() {
210         //申明一个栈对象
211         stack<TNode<T>*> s;
212         //t首先指向根节点
213         TNode<T>* t = root;
214         //申请中间变量,用做判断标识
215         TNode<T>* r;
216         //节点不为空或者栈对象不为空,都进入循环
217         while (t != 0 || !s.empty()) {
218             //如果t节点非空
219             if (t) {
220                 //入栈t节点
221                 s.push(t);
222                 //t节点指向其左孩子
223                 t = t->lchild;
224             } else {
225                 //获取栈顶元素(左孩子指针)
226                 t = s.top();
227                 //判断t的右子树是否存在并且没有访问过
228                 if (t->rchild && t->rchild != r) {
229                     //t节点指向其右孩子
230                     t = t->rchild;
231                     //入栈t节点
232                     s.push(t);
233                     //t节点指向其左孩子
234                     t = t->lchild;
235                 } else {
236                     //获取栈顶元素(左孩子指针)
237                     t = s.top();
238                     //清楚栈顶元素
239                     s.pop();
240                     //直接访问t节点
241                     std::cout << (&t->data) << " ";
242                     //设置已经访问过的节点,防止多次访问(右孩子指针)
243                     r = t;
244                     t = 0;
245                 }
246             }
247         }
248     }
249     /**
250      * 根据模式递归遍历二叉树
251      * 下面代码相对比较简单,只要记住遍历树的规则并且弄一点递归,都可以写出来
252      * 如果在面试中实在写不出来非递归方式,可以写一个递归版本,也许可以争取一个好的工作
253      */
254     void OrderTraverse(const TNode<T>* t, Style mode) {
255         if (t) {
256             //先序遍历二叉树:根左右
257             if (mode == Pre) {
258                 //直接访问t节点
259                 std::cout << (&t->data) << " ";
260                 //递归遍历左子树
261                 this->OrderTraverse(t->lchild, mode);
262                 //递归遍历右子树
263                 this->OrderTraverse(t->rchild, mode);
264             }
265             //中序遍历二叉树:左根右
266             if (mode == In) {
267                 //递归遍历左子树
268                 this->OrderTraverse(t->lchild, mode);
269                 //直接访问t节点
270                 std::cout << (&t->data) << " ";
271                 //递归遍历右子树
272                 this->OrderTraverse(t->rchild, mode);
273             }
274             //后序遍历二叉树:左右根
275             if (mode == Post) {
276                 //递归遍历左子树
277                 this->OrderTraverse(t->lchild, mode);
278                 //递归遍历右子树
279                 this->OrderTraverse(t->rchild, mode);
280                 //直接访问t节点
281                 std::cout << (&t->data) << " ";
282             }
283         }
284     }
285 
286     void test() {
287 
288         ifstream fin("data.txt");
289         this->CreateTreeFromFile(fin);
290         std::cout << "create tree success" << std::endl;
291 
292         std::cout << "create tree after is null ? ";
293         std::cout << boolalpha << this->IsEmpty();
294 
295         std::cout << std::endl;
296         std::cout << "calculated depth of the tree begins" << std::endl;
297         std::cout << "tree is dept = " << this->GetTreeDept(this->GetRoot());
298         std::cout << std::endl;
299         std::cout << "calculated depth of the tree end" << std::endl;
300 
301         std::cout << std::endl;
302         std::cout << "not recursion--------------------begin" << std::endl;
303         std::cout << "pre order traverse: ";
304         this->PreOrderTraverse();
305         std::cout << std::endl;
306 
307         std::cout << "in order traverse: ";
308         this->InOrderTraverse();
309         std::cout << std::endl;
310 
311         std::cout << "post order traverse: ";
312         this->PostOrderTraverse();
313         std::cout << std::endl;
314         std::cout << "not recursion--------------------end" << std::endl;
315 
316         std::cout << std::endl;
317         std::cout << "recursion--------------------begin" << std::endl;
318         std::cout << "pre order traverse:";
319         this->OrderTraverse(this->GetRoot(), Pre);
320         std::cout << std::endl;
321 
322         std::cout << "in order traverse:";
323         this->OrderTraverse(this->GetRoot(), In);
324         std::cout << std::endl;
325 
326         std::cout << "post order traverse:";
327         this->OrderTraverse(this->GetRoot(), Post);
328         std::cout << std::endl;
329         std::cout << "recursion--------------------end" << std::endl;
330     }
331 
332 };
333 
334 #endif /* LINKTREE_H_ */
View Code

Common.h 

 1 /*
 2  * Common.h
 3  *
 4  *  Created on: May 17, 2012
 5  *      Author: sunysen
 6  */
 7 
 8 #ifndef COMMON_H_
 9 #define COMMON_H_
10 
11 #include <iostream>
12 #include <fstream>
13 #include "memory"
14 #include "string"
15 #include "string.h"
16 #include <stdio.h>
17 #include <stdlib.h>
18 #include <math.h>
19 
20 
21 using namespace std;
22 #endif /* COMMON_H_ */
View Code

六:环境

1、运行环境:Ubuntu 10.04 LTS+VMware8.0.4+gcc4.4.3

2、开发工具:Eclipse+make

七:题记

1、上面的代码难免有bug,如果你发现代码写的有问题,请你帮忙指出,让我们一起进步,让代码变的更漂亮和更健壮;

2、我自己能手动写上面代码,离不开郝斌、高一凡、侯捷、严蔚敏等老师的书籍和视频指导,在这里感谢他们;

3、鼓励自己能坚持把更多数据结构方面的知识写出来,让自己掌握更深刻,也顺便冒充下"小牛"

 

 

欢迎继续阅读“启迪思维:数据结构和算法”系列

posted @ 2013-07-25 16:40  sunysen  阅读(1064)  评论(0编辑  收藏  举报