数据结构与算法分析

1.树

描述:自由树是一个连通的,无回路的无向图。

树不是一种线性结构,但它具有一定的线性特征。

树也可以这样定义:树是由根结点和若干颗子树构成的。树是由一个集合以及在该集合上定义的一种关系构成的。集合中的元素称为树的结点,所定义的关系称为父子关系。父子关系在树的结点之间建立了一个层次结构。在这种层次结构中有一个结点具有特殊的地位,这个结点称为该树的根结点,或称为树根。

2.树的属性

节点的深度:约定根节点的深度为0,每个节点的深度为该节点到根节点的唯一通路所经过的边的数目。

节点的层次:相同深度的节点属于同一层次。

节点的度(数):节点的孩子总数。

子树:某节点所有后代及其联边称作子树。

高度:一棵树所有节点中深度的最大值即为高度,单个节点的树高度为0,空树高度为-1

叶节点:没有子节点的节点为叶节点,树中必存在叶节点。

3.二叉树

树的种类非常多,二叉树是树的一个特例,尽管作为一个特例,二叉树也具有自身的一般性。

描述:二叉树是一棵树,其中每个节点都不能有多于两个的儿子。

因为同一个父节点下最多有两个子节点,所以也可以用左右区分二叉树的左右子节点。

真二叉树:不含一度节点的二叉树(可以不含节点)

满二叉树:一棵深度为k,且有2^k-1个节点的树是满二叉树。

完全二叉树:对于深度为K的,有n个结点的二叉树,当且仅当其每一个结点都与深度为K的满二叉树中编号从1至n的结点一一对应时称之为完全二叉树。

如上图,完全二叉树中的每一个节点都可以对应到相同深度的满二叉树上。

满二叉树一定是完全二叉树,反之则不然。

4.二叉树的实现

 从上面我们知道,可以用左右来区分二叉树的两个节点。

二叉树节点的声明:

1 typedef struct TreeNode *PtrNode;
2 typedef struct PtrToNode Tree;
3 
4 struct TreeNode
5 {
6     ElementType Element;
7     Tree Left;
8     Tree Right;
9 };

5.多叉树

描述:树中各节点的子节点数目不确定,且每个节点的子节点均不超过K个的有根树,称为K叉树。

多叉树的实现

父节点表示法:

在树结构中,除根节点以外的节点都是有且仅有一个父节点,按照这个思路,只需在每一个节点中储存对应的父节点的指针,就能将所有节点组织起来。

这样一来,节点的声明就可以写成这样。

1 typedef struct TreeNode_Father *PtrNode;
2 typedef struct PtrToNode Tree;
3 
4 struct TreeNode_Father
5 {
6     ElementType Element;
7     Tree FatherNode;
8 };

这样实现的多叉树,仅需常数时间O(1)即可确定任一节点的父节点,但查找子节点将不得不遍历所有节点,花费O(n)的时间。

子节点表示法:在每个节点中,储存该节点指向的所有子节点的指针。

这样,子节点的查找将能快速实现,但将所有的节点以这样的方法组织起来却并不容易,因为每个节点的子节点数目并不相同,即每个节点并不具有一般性规律,维护起来将很麻烦。

父节点+子节点表示法:如果结合父节点和子节点表示法的优势,在每个节点中同时记录其父节点和所有子节点,就可以高效地兼顾对父节点和子节点的定位。

然而,当此类树涉及到插入删除操作时,每对一个节点进行操作,就必须要对其相应的子序列进行调整,以维护和更新树的拓扑结构。而线性结构完成这些操作都需要花费大量时间。

长子兄弟法:将树的节点的特征抽象出来,我们发现,每个父节点下面都连接着若干(或0)个子节点,把左起第一个子节点记作FirstChild(长子),后面的子节点分别是上一个子节点的NextSibling(兄弟)。

具体的节点声明如下:

1 typedef struct TreeNode *PtrToNode;
2 
3 struct TreeNode
4 {
5     ElementType Element;
6     PtrToNode FirstChild;
7     PtrToNode NextSibling;
8 };

这样实现的一棵树形如下图:

由上我们知道,树结构具有一定的线性特征。

因为同一节点的所有子节点具有线性次序,那么,将长子看作左节点,其他兄弟看作右节点,所有的多叉树都能被看作二叉树。

这种多叉树也叫有序树。

6.BinNode模板类(关于节点的功能

 1 #define BinNodePosi(T) BinNode<T>* //节点位置
 2 #define stature(p) ((p) ? (p)->height : -1) //节点高度(与“空树高度为-1”的约定相统一)
 3 typedef enum { RB_RED, RB_BLACK} RBColor; //节点颜色
 4 
 5 template <typename T> struct BinNode { //二叉树节点模板类
 6 // 成员(为简化描述起见统一开放,读者可根据需要进一步封装)
 7    T data; //数值
 8    BinNodePosi(T) parent; BinNodePosi(T) lc; BinNodePosi(T) rc; //父节点及左、右孩子,均为指针类型
 9    int height; //高度(通用)
10    int npl; //Null Path Length(左式堆,也可直接用height代替)
11    RBColor color; //颜色(红黑树)
12 // 构造函数
13    BinNode() :
14       parent ( NULL ), lc ( NULL ), rc ( NULL ), height ( 0 ), npl ( 1 ), color ( RB_RED ) { }
15    BinNode ( T e, BinNodePosi(T) p = NULL, BinNodePosi(T) lc = NULL, BinNodePosi(T) rc = NULL,
16              int h = 0, int l = 1, RBColor c = RB_RED ) :
17       data ( e ), parent ( p ), lc ( lc ), rc ( rc ), height ( h ), npl ( l ), color ( c ) { }
18 // 操作接口
19    int size(); //统计当前节点后代总数,亦即以其为根的子树的规模
20    BinNodePosi(T) insertAsLC ( T const& ); //作为当前节点的左孩子插入新节点
21    BinNodePosi(T) insertAsRC ( T const& ); //作为当前节点的右孩子插入新节点
22    BinNodePosi(T) succ(); //取当前节点的直接后继
23    template <typename VST> void travLevel ( VST& ); //子树层次遍历
24    template <typename VST> void travPre ( VST& ); //子树先序遍历
25    template <typename VST> void travIn ( VST& ); //子树中序遍历
26    template <typename VST> void travPost ( VST& ); //子树后序遍历
27 // 比较器、判等器(各列其一,其余自行补充),重载运算符
28    bool operator< ( BinNode const& bn ) { return data < bn.data; } //小于
29    bool operator== ( BinNode const& bn ) { return data == bn.data; } //等于
30    /*DSA*/
31    /*DSA*/BinNodePosi(T) zig(); //顺时针旋转
32    /*DSA*/BinNodePosi(T) zag(); //逆时针旋转
33 };

↑代码来自《数据结构(C++语言版)》  邓俊辉

在后续实现中,将频繁用到二叉树的各种功能,不妨用宏定义的方式将这些常用功能整理归纳。

 1 #define IsRoot(x) (!((x).parent))
 2 #define IsLChild(x) (!IsRoot(x)&&(&(x)==(x).parent->lc))
 3 #define IsRChild(x) (!IsRoot(x)&&(&(x)==(x).parent->rc))
 4 #define HasParent(x) (!IsRoot(x))
 5 #define HasLChild(x) ((x).lc)
 6 #define HasRChild(x) ((x).rc)
 7 #define HasChild(x) (HasLChild(x)||HasRChild(x))
 8 #define HasBothChild(x) (HasLChild(x)&&HasRChild(x))
 9 #define IsLeaf(x) (!HasChild(x))
10 #define Sibling(p) (IsLChild(*(p))?(p)->parent->rc:(p)->parent->lc)
11 #define Uncle(x) (IsChild(*((x)->parent))?(x)->parent->parent->rc:(x)->parent->parent->lc)
12 #define FromParentTo(x) (IsRoot(x)?_root:(IsChild(x)?(x).parent->lc:(x).parent->rc))

插入节点:

1 template <typename T> BinNodePosi(T) BinNode<T>::insertAsLC(T const& e)
2 {return lc=new BinNode (e,this);}//作为左子节点插入
3 
4 template <typename T> BinNodePosi(T) BinNode<T>::insertAsRC(T const& e)
5 {return rc=new BinNode (e,this);}//作为右子节点插入

7.BinTree模板(关于树的功能)

 1 #include "BinNode.h" //引入二叉树节点类
 2 template <typename T> class BinTree { //二叉树模板类
 3 protected:
 4    int _size; BinNodePosi(T) _root; //规模、根节点
 5    virtual int updateHeight ( BinNodePosi(T) x ); //更新节点x的高度
 6    void updateHeightAbove ( BinNodePosi(T) x ); //更新节点x及其祖先的高度
 7 public:
 8    BinTree() : _size ( 0 ), _root ( NULL ) { } //构造函数
 9    ~BinTree() { if ( 0 < _size ) remove ( _root ); } //析构函数
10    int size() const { return _size; } //规模
11    bool empty() const { return !_root; } //判空
12    BinNodePosi(T) root() const { return _root; } //树根
13    BinNodePosi(T) insertAsRoot ( T const& e ); //插入根节点
14    BinNodePosi(T) insertAsLC ( BinNodePosi(T) x, T const& e ); //e作为x的左孩子(原无)插入
15    BinNodePosi(T) insertAsRC ( BinNodePosi(T) x, T const& e ); //e作为x的右孩子(原无)插入
16    BinNodePosi(T) attachAsLC ( BinNodePosi(T) x, BinTree<T>* &T ); //T作为x左子树接入
17    BinNodePosi(T) attachAsRC ( BinNodePosi(T) x, BinTree<T>* &T ); //T作为x右子树接入
18    int remove ( BinNodePosi(T) x ); //删除以位置x处节点为根的子树,返回该子树原先的规模
19    BinTree<T>* secede ( BinNodePosi(T) x ); //将子树x从当前树中摘除,并将其转换为一棵独立子树
20    template <typename VST> //操作器
21    void travLevel ( VST& visit ) { if ( _root ) _root->travLevel ( visit ); } //层次遍历
22    template <typename VST> //操作器
23    void travPre ( VST& visit ) { if ( _root ) _root->travPre ( visit ); } //先序遍历
24    template <typename VST> //操作器
25    void travIn ( VST& visit ) { if ( _root ) _root->travIn ( visit ); } //中序遍历
26    template <typename VST> //操作器
27    void travPost ( VST& visit ) { if ( _root ) _root->travPost ( visit ); } //后序遍历
28    bool operator< ( BinTree<T> const& t ) //比较器(其余自行补充)
29    { return _root && t._root && lt ( _root, t._root ); }
30    bool operator== ( BinTree<T> const& t ) //判等器
31    { return _root && t._root && ( _root == t._root ); }
32    /*DSA*/
33    /*DSA*/void stretchToLPath() { stretchByZag ( _root ); } //借助zag旋转,转化为左向单链
34    /*DSA*/void stretchToRPath() { stretchByZig ( _root, _size ); } //借助zig旋转,转化为右向单链
35 }; //BinTree

高度更新:

二叉树任一节点的高度,都等于其子节点的最大高度+1

当某一节点的高度发生改变时,其所有祖先的高度都将发生改变。

那么,我们可以从当前节点(高度首先发生变化的节点)开始,沿着parent指针的方向,向上回溯,依次更新其所有祖先的高度。

1 template <typename T> int BinTree<T>::updateHeight ( BinNodePosi(T) x ) //更新节点x高度
2 { return x->height = 1 + max ( stature ( x->lc ), stature ( x->rc ) ); } //具体规则,因树而异
3 
4 template <typename T> void BinTree<T>::updateHeightAbove ( BinNodePosi(T) x ) //更新高度
5 { while ( x ) { updateHeight ( x ); x = x->parent; } } //从x出发,覆盖历代祖先。可优化

节点插入:

每次插入记得更新树的高度和全树规模

1 template <typename T> BinNodePosi(T) BinTree<T>::insertAsRoot ( T const& e )
2 { _size = 1; return _root = new BinNode<T> ( e ); } //将e当作根节点插入空的二叉树
3 
4 template <typename T> BinNodePosi(T) BinTree<T>::insertAsLC ( BinNodePosi(T) x, T const& e )
5 { _size++; x->insertAsLC ( e ); updateHeightAbove ( x ); return x->lc; } //e插入为x的左孩子
6 
7 template <typename T> BinNodePosi(T) BinTree<T>::insertAsRC ( BinNodePosi(T) x, T const& e )
8 { _size++; x->insertAsRC ( e ); updateHeightAbove ( x ); return x->rc; } //e插入为x的右孩子

子树接入:

将要接入的子树的根节点赋给某节点的子节点

 1 template <typename T> //二叉树子树接入算法:将S当作节点x的左子树接入,S本身置空
 2 BinNodePosi(T) BinTree<T>::attachAsLC ( BinNodePosi(T) x, BinTree<T>* &S ) { //x->lc == NULL
 3    if ( x->lc = S->_root ) x->lc->parent = x; //接入
 4    _size += S->_size; updateHeightAbove ( x ); //更新全树规模与x所有祖先的高度
 5    S->_root = NULL; S->_size = 0; release ( S ); S = NULL; return x; //释放原树,返回接入位置
 6 }
 7 
 8 template <typename T> //二叉树子树接入算法:将S当作节点x的右子树接入,S本身置空
 9 BinNodePosi(T) BinTree<T>::attachAsRC ( BinNodePosi(T) x, BinTree<T>* &S ) { //x->rc == NULL
10    if ( x->rc = S->_root ) x->rc->parent = x; //接入
11    _size += S->_size; updateHeightAbove ( x ); //更新全树规模与x所有祖先的高度
12    S->_root = NULL; S->_size = 0; release ( S ); S = NULL; return x; //释放原树,返回接入位置
13 }

子树删除:

 1 template <typename T> //删除二叉树中位置x处的节点及其后代,返回被删除节点的数值
 2 int BinTree<T>::remove ( BinNodePosi(T) x ) { //assert: x为二叉树中的合法位置
 3    FromParentTo ( *x ) = NULL; //切断来自父节点的指针
 4    updateHeightAbove ( x->parent ); //更新祖先高度
 5    int n = removeAt ( x ); _size -= n; return n; //删除子树x,更新规模,返回删除节点总数
 6 }
 7 template <typename T> //删除二叉树中位置x处的节点及其后代,返回被删除节点的数值
 8 static int removeAt ( BinNodePosi(T) x ) { //assert: x为二叉树中的合法位置
 9    if ( !x ) return 0; //递归基:空树
10    int n = 1 + removeAt ( x->lc ) + removeAt ( x->rc ); //递归释放左、右子树
11    release ( x->data ); release ( x ); return n; //释放被摘除节点,并返回删除节点总数
12 }

子树分离:

1 template <typename T> //二叉树子树分离算法:将子树x从当前树中摘除,将其封装为一棵独立子树返回
2 BinTree<T>* BinTree<T>::secede ( BinNodePosi(T) x ) { //assert: x为二叉树中的合法位置
3    FromParentTo ( *x ) = NULL; //切断来自父节点的指针
4    updateHeightAbove ( x->parent ); //更新原树中所有祖先的高度
5    BinTree<T>* S = new BinTree<T>; S->_root = x; x->parent = NULL; //新树以x为根
6    S->_size = x->size(); _size -= S->_size; return S; //更新规模,返回分离出来的子树
7 }

8.二叉树的遍历方法

我们根据访问根节点的次序来分类二叉树的遍历方法。

根节点(V),左子节点(L),右子节点(R)

先序遍历:V | L | R

中序遍历:L | V | R

后序遍历:L | R | V

层序遍历:从左到右按层遍历

9.先序遍历

递归版本:

由于二叉树本身的定义便带有递归的性质,所以很容易得出递归解法

1 template <typename T,typename VST>//元素类型,操作器
2 void travPre_R (BinNodePosi(T) x,VST& visit)
3 {
4     if(!x) return;
5     visit(x->data);
6     travPre_R(x->lc,visit);
7     travPre_R(x->rc,visit);
8 }

迭代版本1:

因为递归版本的时间、空间复杂度的常系数较大,所以虽然其整体复杂度只有O(n),但实际应用时效率不高。

将递归改写为迭代可以有效将时间空间复杂度常系数缩小。

观察递归版本可知,该实现是尾递归的形式,改写成迭代很容易。

 1 template <typename T,typename VST>//元素类型,操作器
 2 void travPre_I1(BinNodePosi(T) x,VST& visit)
 3 {
 4     Stack<BinNodePosi(T)> S;//辅助栈
 5     if(x) S.push(x);//根节点入栈
 6     while(!S.empty())//栈空前一直循环
 7     {
 8         x=S.pop();
 9         visit(x->data);
10         if(HasRChild(*x))//入栈顺序是先右后左
11             S.push(x->rc);
12         if(HasLChild(*x))
13             S.push(x->lc);//出栈顺序是先左后右
14     }
15 }

迭代版本2:

版本1并不适用于尾递归以外的场景,当我们试图将这种思路推广到中序遍历和后序遍历时将遇到困难。

另一个思路是,观察二叉树的遍历顺序,可以得知,遍历开始时,总是从根节点出发,访问左子节点,然后访问左子节点的左子节点... ...直到遇到一个节点没有左子节点时,将这个节点记作v,访问v的右子节点,然后向上回溯,访问v的父节点的右子节点(即v的兄弟节点),然后从v的兄弟节点出发,重复“访问左子节点,向上回溯”的过程... ...

简而言之,从当前节点开始,沿左分支不断深入,直到没有左节点的分支,从该节点开始自下而上访问其右子树。访问右子树的方式与上面相同。

代码实现:

 1 //从当前节点出发,沿左分支不断深入,直至没有左分支的节点;沿途节点遇到后立即访问
 2 template <typename T, typename VST> //元素类型、操作器
 3 static void visitAlongLeftBranch ( BinNodePosi(T) x, VST& visit, Stack<BinNodePosi(T)>& S ) {
 4    while ( x ) {
 5       visit ( x->data ); //访问当前节点
 6       S.push ( x->rc ); //右孩子入栈暂存(可优化:通过判断,避免空的右孩子入栈)
 7       x = x->lc;  //沿左分支深入一层
 8    }
 9 }
10 
11 template <typename T, typename VST> //元素类型、操作器
12 void travPre_I2 ( BinNodePosi(T) x, VST& visit ) { //二叉树先序遍历算法(迭代版#2)
13    Stack<BinNodePosi(T)> S; //辅助栈
14    while ( true ) {
15       visitAlongLeftBranch ( x, visit, S ); //从当前节点出发,逐批访问
16       if ( S.empty() ) break; //直到栈空
17       x = S.pop(); //弹出下一批的起点
18    }
19 }

后序遍历可以直接沿用迭代版本2的思路。

10.中序遍历

递归版本:

和前序遍历一样,递归版本可以很简洁地实现

1 template <typename T,typename VST>//元素类型,操作器
2 void travIn_R (BinNodePosi(T) x,VST& visit)
3 {
4     if(!x) return;
5     travIn_R(x->lc,visit);
6     visit(x->data);
7     travIn_R(x->rc,visit);
8 }

不同的是,中序遍历并不是尾递归,不能将其直接改写为迭代版本。

迭代版本1:

中序遍历从根节点开始,首先访问的却不是根节点,而是整棵树最左边的叶节点。控制权由根节点开始,依次向下交给左子节点,直到某节点没有左子节点,访问该节点,然后访问其右子树(如果有的话),然后向上回溯到该节点的父节点,访问其右子树... ...

概括起来就是,中序遍历可以沿其左通路,以沿途节点为界,将整个中序遍历的过程划分为d+1段。

每一段的过程是,访问左通路上的某节点,再遍历其对应的右子树。一段完成以后,转到左通路上的上一个节点,重复该过程。

↑中序遍历过程的分解

具体代码实现为:

 1 template <typename T> //从当前节点出发,沿左分支不断深入,直至没有左分支的节点
 2 static void goAlongLeftBranch ( BinNodePosi(T) x, Stack<BinNodePosi(T)>& S ) {
 3    while ( x ) { S.push ( x ); x = x->lc; } //当前节点入栈后随即向左侧分支深入,迭代直到无左孩子
 4 }
 5 
 6 template <typename T, typename VST> //元素类型、操作器
 7 void travIn_I1 ( BinNodePosi(T) x, VST& visit ) { //二叉树中序遍历算法(迭代版#1)
 8    Stack<BinNodePosi(T)> S; //辅助栈
 9    while ( true ) {
10       goAlongLeftBranch ( x, S ); //从当前节点出发,逐批入栈
11       if ( S.empty() ) break; //直至所有节点处理完毕
12       x = S.pop(); visit ( x->data ); //弹出栈顶节点并访问之
13       x = x->rc; //转向右子树
14    }
15 }

首先调用函数goAlongLeftBranch,沿左通路向下,到达最左侧的叶节点,利用辅助栈记录沿途的节点,用后进先出的性质实现实际的访问顺序。

直接后继:中序遍历的大致思路还是将树这样一种非线性结构转化为线性结构,使其遍历按照线性的顺序,而一旦树具有了线性的顺序,和向量链表等一样,二叉树的节点一样具有其前驱和后继,而确定二叉树节点的直接后继,将在后续算法中起到重要的作用。

代码实现如下:

 1 template <typename T> BinNodePosi(T) BinNode<T>::succ() { //定位节点v的直接后继
 2    BinNodePosi(T) s = this; //记录后继的临时变量
 3    if ( rc ) { //若有右孩子,则直接后继必在右子树中,具体地就是
 4       s = rc; //右子树中
 5       while ( HasLChild ( *s ) ) s = s->lc; //最靠左(最小)的节点
 6    } else { //否则,直接后继应是“将当前节点包含于其左子树中的最低祖先”,具体地就是
 7       while ( IsRChild ( *s ) ) s = s->parent; //逆向地沿右向分支,不断朝左上方移动
 8       s = s->parent; //最后再朝右上方移动一步,即抵达直接后继(如果存在)
 9    }
10    return s;
11 }

迭代版本2:对迭代版本1进行改写简化

 1 template <typename T, typename VST> //元素类型、操作器
 2 void travIn_I2 ( BinNodePosi(T) x, VST& visit ) { //二叉树中序遍历算法(迭代版#2)
 3    Stack<BinNodePosi(T)> S; //辅助栈
 4    while ( true )
 5       if ( x ) {
 6          S.push ( x ); //根节点进栈
 7          x = x->lc; //深入遍历左子树
 8       } else if ( !S.empty() ) {
 9          x = S.pop(); //尚未访问的最低祖先节点退栈
10          visit ( x->data ); //访问该祖先节点
11          x = x->rc; //遍历祖先的右子树
12       } else
13          break; //遍历完成
14 }

迭代版本3:

以上两个版本都需要用到辅助栈,而在最坏情况下,辅助栈所占用的空间规模将和二叉树的规模相当,为此,借助parent指针和succ(直接后继定位函数),可以有效节约空间开销。

时间复杂度有所牺牲。

 1 template <typename T, typename VST> //元素类型、操作器
 2 void travIn_I3 ( BinNodePosi(T) x, VST& visit ) { //二叉树中序遍历算法(迭代版#3,无需辅助栈)
 3    bool backtrack = false; //前一步是否刚从右子树回溯——省去栈,仅O(1)辅助空间
 4    while ( true )
 5       if ( !backtrack && HasLChild ( *x ) ) //若有左子树且不是刚刚回溯,则
 6          x = x->lc; //深入遍历左子树
 7       else { //否则——无左子树或刚刚回溯(相当于无左子树)
 8          visit ( x->data ); //访问该节点
 9          if ( HasRChild ( *x ) ) { //若其右子树非空,则
10             x = x->rc; //深入右子树继续遍历
11             backtrack = false; //并关闭回溯标志
12          } else { //若右子树空,则
13             if ( ! ( x = x->succ() ) ) break; //回溯(含抵达末节点时的退出返回)
14             backtrack = true; //并设置回溯标志
15          }
16       }
17 }

用一个布尔类型的变量backtrack指示此前是否已经做过一次自下而上的回溯,节省栈的空间开销。

但也因此无法直接获得回溯时的访问节点的顺序(不能借助栈后进先出的性质),需要反复调用succ接口,不断确定当前节点的直接后继,以确定访问顺序。

当succ返回的直接后继为NULL时,也就意味着遍历完成了。

11.层次遍历

层次遍历非常好理解,从左往右逐层向下访问节点即可。

上面的先序遍历中序遍历采用了辅助栈,而层次遍历则采用队列结构。

 1 /*DSA*/#include "../queue/queue.h" //引入队列
 2 template <typename T> template <typename VST> //元素类型、操作器
 3 void BinNode<T>::travLevel ( VST& visit ) { //二叉树层次遍历算法
 4    Queue<BinNodePosi(T)> Q; //辅助队列
 5    Q.enqueue ( this ); //根节点入队
 6    while ( !Q.empty() ) { //在队列再次变空之前,反复迭代
 7       BinNodePosi(T) x = Q.dequeue(); visit ( x->data ); //取出队首节点并访问之
 8       if ( HasLChild ( *x ) ) Q.enqueue ( x->lc ); //左孩子入队
 9       if ( HasRChild ( *x ) ) Q.enqueue ( x->rc ); //右孩子入队
10    }
11 }

12.二叉树的应用

表达式树,PFC编码树,Huffman编码树

挖坑待填。

填坑,数据结构应用 - 二叉树

参考资料【1】《数据结构(C++语言版)》  邓俊辉

    【2】《数据结构与算法分析——C语言描述》    Mark Allen Weiss

原文地址:https://www.cnblogs.com/CofJus/p/10348500.html