バイナリデータ構造(IV)

ツリー

ベクトルとリストは、彼らが考慮に動的および静的操作の効率を取ることができない、明白な弱点です。

利点は、それがリストList <一覧>半線形構造のリストと考えることができ、ツリーベクトルリストとみなされ、一緒に組み合わされてもよいです。

 

アプリケーション

階層関係を表します

 

数学的には、ツリーは、図ユニコム非環式図の特別な種類です。

ツリー(頂点)から頂点の集合と組成の間複数のエッジ(エッジ)に接続されています。

コンピュータサイエンスでは、root(ルート)と呼ばれる特定の頂点を指定します

ルートを指定した後、私たちは木があると言う根付いツリー(根ざし木)

ノードと呼ばれる手続きポイント実装から、Iより頂点(頂点)(ノード)

 

大ルート付きツリーになることがあり、互いに、小さなルート付きツリー内でネストすることによって

ルート付きツリーの任意のセットについて、新しい頂点と根元との間に新たな頂点を導入することにより、大規模にルート付きツリーを構成するために対応するツリーも導入側のルートを持っていました。

 

 

 

 

 R 私は(子供)のR子と呼ばれる、R iの間でお互いを呼び出す(兄弟)の兄弟、彼らのrは(親)の父親を

D =度(r)は(A)のRである(°)

子どもの総数ノードvは、ノードの学位または度(度)と呼ばれる、子ノードはルートノードを含め、リーフノード(リーフノード)と呼ばれていない、残りの部分を含むすべての内部ノード(内部ノード)です。

 

nは頂点の数であり、Eはエッジの数であり、

 

 エッジ任意の数のツリーに含まれる程度のすべての頂点の和に正確に等しく、また、頂点の総数から1を引いたものに等しい、頂点の数と木のエッジの数は同じオーダーです。

 

 

 

 

 

 

 

 

 ツリーがあります

図非環式通信

図最小限の通信

グレート非循環グラフ

いずれかのユニークなルートノードの存在およびVパス間のパス(V、R)=パス(V)

したがって、各ノードは、すなわち、一意のインデックスを持つルートノードへの経路の長さを

ルートが指定されると、他のノードは、決定されたインデックスを受信します

Vと呼ばれる、そこを通ってエッジ数にルートノードR vにそれぞれ固有の経路深さ(深さ)、(V)の深さと呼ばれます

合意されたルートの深さ、すなわち、深さ(R)= 0、レイヤ0に属し

 

任意の差がなければ、パスは、ノードおよびサブツリーはお互いを参照することができ

 

パス(v)はノードで、Vの先祖(祖先)であり、Vは、それらの子孫(子孫)であります

独自のに加えて、それは(正しい)祖先/子孫

これは、すべてのノードのルートである共通の祖先

 

SEMILINEAR:任意の先祖で深さ、されて存在する場合、vは、/子孫であるバインド / ないユニーク

そのノードが独自の祖先を持っているが、必ずしもそうではないだけで子孫、一意の前駆体が保証されているが、後継者の一意性保証はありません

 

いいえ先祖ノードがないルートノード、どのノードの子孫ではありませんリーフノード

 

すべての葉は高さの最大深さ(サブ)ツリー(ルート)の呼ばれました

空の木の高さとしている-1

深さ(V)+高さ(V)<=高さ(T)

 

第二に、式ツリー

インターフェース

ノード 機能
ルート() ルート
親()
第一子() 長男
nextSibling() ブラザーズ
挿入(I、E) それぞれの子の挿入などのI電子
(i)を削除 i番目のそれぞれの子(とその子孫)を削除します。
トラバース() トラバーサル

 

木があります

観察:ルートの外に、任意のノードが一つだけの親ノードを持ち、

概念:ノードがシーケンスとして編成され、各ノードが記録されました

データ情報そのもの

親親ランクまたは位置

 

 

 

 

 

 すべてのノードが1つのシーケンスに収束する、差は、基準点は、すべての子供たちが小さなデータセットを構成することで、各ノードの子を準備参照と呼ばれています

 

 見上げるの利点の損失は、全体の線状配列を横断しなければなりませんでした

 

上記2つの線形配列の組み合わせ

 

 

仍然存在不足,children数据集在规模上可能十分悬殊

 

长子+兄弟

 

 

 

 

三、二叉树概述

 二叉树虽然是树的子集,但施加了某些条件之后,二叉树可以代表所有的树

 

节点度数不超过2的树,称作二叉树(binary tree)

 

 

 

同一节点的孩子和子树,均以左右区分,

 

 

 隐含有序,左在先,右在后

 

基数

深度为k的节点,至多2k

 

 

 

 

 

 满树:

 

 

 

 

二叉树在横向上的宽度与纵向上的高度呈指数的关系,宽度是高度的指数

 

 

 二叉搜索树的基础

 

把出度记在二叉树上

 

 

 

真二叉树,每个节点的出度都是偶数或者零

 

可以假想为每个节点添加足够多的孩子节点,算法就可更简单实现

 

 

 

描述多叉树

二叉树是多叉树的特例,但在有根且有序时,其描述能力足以覆盖后者

多叉树可以转化并表示为二叉树,回忆长子-兄弟表示法...

 

 

 

四、二叉树实现

BinNode模板

 

 

 

#define BinNode(T) BinNode<T> //节点位置
template <typename T> struct BinNode {
    BinNodePosi(T) parent, lChild, rChild; // 父亲,孩子
    T data; int height; int size(); // 高度,子树规模
    BinNodePosi(T) insertAslC(T const &); // 作为左孩子插入新节点
    BinNodePosi(T) insertAsRC(T const &); // 作为右孩子插入新节点
    BinNodePosi(T) succ(); // (中序遍历意义下)当前节点的直接后继
    template <typename VST> void travLevel(VST &); // 子树层次遍历
    template <typename VST> void travPre(VST &); // 子树先序遍历
    template <typename VST> void travIn(VST &); // 子树中序遍历
    template <typename VST> void travPost(VST &); // 子树后序遍历
};

接口实现

template <typename T> class BinTree
{
protected:
    int _size; // 规模
    BinNodePosi(T) _root; // 根节点
    virtual int updateHeight(BinNodePosi(T) x); // 更新节点x的高度
    void updateHeightAbove(BinNodePosi(T) x); // 更新x及其祖先的高度
public:
    int size() const { return _size; } // 规模
    bool empty() const { return !_root; } // 判空
    BinNodePosi(T) root() const { return _root; } // 树根

    /* 子树接入,删除和分离接口 */
    /* 遍历接口 */
};

高度更新

只有单个节点,不存在任何节点的树(空树),正常情况如何统一?

 

 

 通过宏定义封装,重新命名等价意义上的高度,

#define stature(p)((p)? (p)->height :-1) // 节点高度--约定空树高度为-1

节点的高度之所以会发生变化,是因为左孩子或是右孩子的高度发生了变化

一个节点的高度等于其左孩子或右孩子高度的最大者,再加1

template <typename T> //更新节点x高度,具体规则因树不同而异
int BinTree<T>::updateHeight(BinNodePosi(T) x) {
    return x->height = 1 + max(stature(x->lChild), stature(x->rChild));
} // 此处采用常规二叉树规则,0(1)

节点插入

 

 

 

template <typename T> BinNodePosi(T)
BinTree<T>::insertAsRC(BinNodePosi(T) x, T const & e) { // insertAsLC()对称
    _size++; x->insertAsRC(e); // x祖先的高度可能增加,其余节点比如不变
    updateHeightAbove(x);
    return x->rChild;
}

 

五、先序遍历

按照某种次序,访问树中各节点,每个节点被访问恰好一次

 

 

 

 

 

 

 

 

 

 

递归

template <typename T, typename VST>
void traverse(BinNodePosi(T) x, VST & visit) {
    if (!x) return;
    visit(x->data);
    traverse(x->lChild, visit);
    traverse(x->rChild, visit);
} // T(n) =0(1) + T(a) + T(n-a-1) = O(n)

 

 

 

迭代1:实现

引入栈

template <typename T, typename VST>
void travPre_I1(BinNodePosi(T) x, VST & visit) {
    Stack <BinNodePosi(T)> S; // 辅助栈
    if (x) S.push(x); // 根节点入栈
    // 在栈变空之前反复循环
    while (!S.empty() 
    {
        x = S.pop(); visit(x->data); // 弹出并访问当前节点
        if (HasRchild(*x)) S.push(x->rChild); // 右孩子先入后出
        if (HasLChild(*x)) S.push(x->lChild); // 左孩子后入先出
    } // 体会以上两句的次序
}

 

 

 先父,后左孩子,再右孩子

 

 

 

 

迭代2:思路

 

 

 

总是沿着左侧分支下行的链叫当前子树的左侧链

 

 

 

 

迭代2:实现

template <typename T, typename VST>
static void  visitAlongLeftBranch(
    BinNodePosi(T) x,
    VST & visit,
    Stack <BinNodePosi(T)> & S
)
{
    while (x){ // 反复地
        visit(x->data); // 访问当前节点
        S.push(x->rChild); // 右孩子(右子树)入栈(将来逆序出栈)
        x = x->lChild; // 沿左侧链下行
    } // 只有右孩子、Null可能入栈--增加判断以剔除后者,是否值得?
}

左算法

template <typename T, typename VST>
void travPre_I2(BinNodePosi(T) x, VST & visit) {
    stack <BinNodePosi(T)> S; // 辅助栈
    while (true) { // 以(右)子树为单位,逐批访问节点
        visitAlongLeftBranch(x, visit, S); // 访问子树x的左侧链,右子树入栈缓冲
        if (S.empty()) break; // 栈空即推出
        x = S.pop(); // 弹出下一子树的根
    } // #pop = #push =#visit = O(n) = 分摊O(1)
}

迭代2:实例

^是空

 

 

 

六、中序遍历

递归

template <typename T, typename VST>
void traverse(BinNodePosi(T) x, VST & visit) {
    if (!x) return;
    traverse(x->lChild, visit);
    visit(x->data);
    traverse(x->rChild, visit);
} // T(n) =0(1) + T(a) + T(n-a-1) = O(n)

观察

 

 

 没有左孩子相当于左孩子被访问过了

 

思路

访问左侧链节点,遍历右子树

 

 

 实现

template <typename T>
static void goAlongLeftBranch(BinNodePosi(T) x, Stack <BinNodePosi(T)> & S)
{
    while (x) { S.push(x); x = x->lChild; } // 反复入栈,沿左侧分支深入
}

左算法

template<typename T, typename V> void travIn_I1(BinNodePosi(T) x, V& visit) {
    Stack <BinNodePosi(T)> S; // 辅助栈
    while (true) // 反复地 
    {
        goAlongLeftBranch(x, S); // 从当前节点触发,逐批入栈
        if (S.empty()) break; // 直至所有节点处理完毕
        x = S.pop(); // x的左子树或为空,或已遍历(等效于空),故可以
        visit(x->data); // 立即访问之
        x = x->rChild; // 再转向其右子树(可能为空,留意处理手法)
    }
}

 

实例

 

 

 

七、层次遍历

在垂直方向按深度将所有节点划分为若干等价类,有根性就是垂直方向的次序,同一深度的节点如何定义次序呢?

可以根据垂直方向和水平方向的次序定义整体的次序,而进行遍历

具体说,自高向低,在每一层自左向右,逐一访问树中的每一节点,层次遍历

前面的策略都有后代先于祖先访问的情况,即逆序,无论隐式的还是显式的都需要借助栈结构

而在层次遍历中,所有节点都严格按照深度次序,由高到低的接收访问,严格满足顺序性

队列大显身手

 

实现

template <typename T> template <typename VST>
void BinNode<T>::travLevel(VST & visit) { // 二叉树层次遍历
    Queue<BinNodePosi(T)> Q; // 引入辅助队列
    Q.enqueue(this); // 根节点入队
    while (!Q.empty() { // 在队列再次变空之前,反复迭代
        BinNodePosi(T) x = Q.dequeue(); // 取出队首节点,并随即
        visit(x->data); // 访问之
        if (HasLChild(*x)) Q.enqueue(x->lChild)); // 左孩子入队
        if (HasRChild(*x)) Q.enqueue(x->rChild); // 右孩子入队
    }
}

左先右后

 

A节点入队,右侧的是现在队列的照片

 

 

 取出队首的节点

 

 

 左顾右盼,A只有左孩子B,B入队

 

 

 取出队首节点B

 

 

 

对B进行访问,左顾右盼,左右孩子都有,都要入队,左先右后

 

 

 取出队首节点C

 

 

 访问C节点,左顾右盼,都是空的

取出队首节点D

 

 

 访问D,左顾右盼,左右孩子都有,都要入队

 

 

 取出队首节点E

 

 

 访问E,左顾右盼,发现右孩子G,入队

 

 

 取出队首元素F

访问F,没有左右孩子,进入下一步迭代

取出队首G,访问G,左顾右盼,没有左右孩子

 

 

 层次遍历完成

 

八、重构

由任何一棵二叉树,都可以导出三个序列:先序、中序和后序遍历序列

它们都是由树中的所有节点,依照对应的遍历策略所确定的次序,依次排列而成

如果已知某棵树的遍历序列,是否可以忠实还原出树的拓扑结构?

 

只需要中序遍历序列,和先序、后序中两者中的一个就可以还原树的结构

数学归纳

 

おすすめ

転載: www.cnblogs.com/aidata/p/11515652.html