• 数据结构与算法分析


    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

  • 相关阅读:
    kettle安装及初步使用
    Datax初步使用
    可修改性及其实现战术(hot words)
    淘宝网的六个质量属性
    python数据可视化笔记---——matplotlib.pyplot()
    Linux虚拟环境virtualevn
    deepin安装虚拟环境virtualenv
    python面试题-web后端
    rabbitmq和redis用作消息队列的区别
    nginx配置项概括说明
  • 原文地址:https://www.cnblogs.com/CofJus/p/10348500.html
Copyright © 2020-2023  润新知