樹(shù)、森林、二叉樹(shù)與并查集

樹(shù)

在n個(gè)結(jié)點(diǎn)的樹(shù)中有n-1條邊。
樹(shù)中一個(gè)結(jié)點(diǎn)的子結(jié)點(diǎn)個(gè)數(shù)稱為該結(jié)點(diǎn)的度,樹(shù)中結(jié)點(diǎn)的最大度數(shù)稱為樹(shù)的度。
有序樹(shù)和無(wú)序樹(shù)(左右子樹(shù)是否有順序)
路徑只能從上到下,同一雙親結(jié)點(diǎn)的兩個(gè)孩子結(jié)點(diǎn)之間不存在路徑。
樹(shù)的性質(zhì)
1.結(jié)點(diǎn)數(shù)等于所有結(jié)點(diǎn)的度數(shù)加1
2.度為m的樹(shù)中第i層上至多有m^(i-1)個(gè)結(jié)點(diǎn)
3.高度h的m叉樹(shù)至多有(m^h-1)/(m-1)個(gè)結(jié)點(diǎn)
4.具有n個(gè)結(jié)點(diǎn)的m叉樹(shù)的最小高度為:向上取整(log(m)(n(m-1)+1))


二叉樹(shù)

滿二叉樹(shù)
高度為h的,含有2^h-1個(gè)結(jié)點(diǎn)
若從1開(kāi)始,自上而下,自左向右對(duì)結(jié)點(diǎn)編號(hào),i結(jié)點(diǎn)的父節(jié)點(diǎn)為i/2向下取整,左孩子2i,右孩子2i+1
完全二叉樹(shù)
每個(gè)結(jié)點(diǎn)的編號(hào)都與與其同高度的滿二叉樹(shù)一致
1.若i<=n/2向下取整,則i為分支結(jié)點(diǎn),否則為葉子結(jié)點(diǎn)
2.葉子結(jié)點(diǎn)只可能在層次最大的兩層上出現(xiàn)。對(duì)于最大層次中的葉子結(jié)點(diǎn),都依次排列在該層的最左邊
3.若有度為1的結(jié)點(diǎn),則只可能有一個(gè),且該結(jié)點(diǎn)只有左孩子
4.按層序編號(hào)后,一旦出現(xiàn)某編號(hào)為i的結(jié)點(diǎn)為葉子結(jié)點(diǎn)或只有左孩子,則編號(hào)大于i的結(jié)點(diǎn)均為葉子結(jié)點(diǎn)
5.若n為奇數(shù)則每個(gè)分支結(jié)點(diǎn)都既有左節(jié)點(diǎn)又有右節(jié)點(diǎn)
6.i所在的層次為:向下取整(log(2)i)+1
7.具有n個(gè)結(jié)點(diǎn)的完全二叉樹(shù)的高度為:向上取整(log(2)(n+1))或向下取整(log(2)n)+1

滿二叉樹(shù)與完全二叉樹(shù).PNG

二叉排序樹(shù)
左子樹(shù)上的所有結(jié)點(diǎn)的關(guān)鍵字均小于根結(jié)點(diǎn)的關(guān)鍵字,右子樹(shù)上所有結(jié)點(diǎn)的關(guān)鍵字均大于根結(jié)點(diǎn)的關(guān)鍵字。左子樹(shù)和右子樹(shù)又各是一顆二叉排序樹(shù)。
平衡二叉樹(shù)
任一結(jié)點(diǎn)的左子樹(shù)和右子樹(shù)的深度之差不超過(guò)1

二叉樹(shù)的性質(zhì)
1.度為0,1,2的結(jié)點(diǎn)個(gè)數(shù)分別為n0,n1,n2
結(jié)點(diǎn)數(shù)(n0+n1+n2)-1=分支數(shù)=n1+2*n2
-》n0=n2+1(葉節(jié)點(diǎn)數(shù))
2.非空二叉樹(shù)第k層上至多有2^(k-1)個(gè)結(jié)點(diǎn)


二叉樹(shù)的存儲(chǔ)結(jié)構(gòu)

完全二叉樹(shù)和滿二叉樹(shù)比較適合順序存儲(chǔ),通過(guò)序號(hào)來(lái)反映父子關(guān)系
二叉樹(shù)一般采用鏈?zhǔn)酱鎯?chǔ)結(jié)構(gòu)

typedef struct BiTNode
{
    ElemType data;
    struct BiTNode *lchild,*rchild;
}BiTNode,*BiTree;

在含有n個(gè)結(jié)點(diǎn)的二叉鏈表中,含有n+1個(gè)空鏈域。


二叉樹(shù)的遍歷

按照根結(jié)點(diǎn)被訪問(wèn)的時(shí)機(jī),分為先序NLR,中序LNR,后序LRN
遞歸

void PreOrder(BiTree T)
{
    if(T!=NULL){
        visit(T);
        PreOrder(T->lchild);
        PreOrder(T->rchild);
    }
}

每個(gè)結(jié)點(diǎn)都訪問(wèn)一次僅訪問(wèn)一次所以時(shí)間復(fù)雜度為O(n)
遞歸棧的深度恰好為樹(shù)的深度
非遞歸

void InOrder2(BiTree T)
{
    InitStack(S); BiTree p=T;
    while(p||!IsEmpty(S))
    {
        if(p){
            Push(S,p);
            p=p->lchild;
        }
        else{
            Pop(S,p); visit(p);
            p=p->rchild;
        }
    }
}

p首先指向根節(jié)點(diǎn),將p入棧,并使p指向其左節(jié)點(diǎn),若p為null(沒(méi)有左節(jié)點(diǎn)),則出棧并訪問(wèn),再將p指向其右節(jié)點(diǎn)。

后序的非遞歸
因?yàn)樵谧?,右子?shù)都處理完畢之前不可將根從棧中移除,則需要在入棧時(shí)添加額外信息rvisited來(lái)標(biāo)識(shí),是從左子樹(shù)回來(lái)的還是從右樹(shù)回來(lái)的。

typedef struct{
    BTNode *p;
    int rvisited;   //為1的時(shí)候代表p所指向的結(jié)點(diǎn)的右節(jié)點(diǎn)已被訪問(wèn)過(guò)
}SNode;     //棧中的結(jié)點(diǎn)定義
typedef struct{
    SNode Elem[maxsize]
    int top;
}SqStack;    //棧結(jié)構(gòu)體
void PostOrder2(BiTree T)
{
    SNode sn;
    BTNode *pt=T
    InitStack(S);
    while(T){     //向左走到盡頭,并把路徑上的元素全部入棧
        Push(pt,0);
        pt=pt->lchild;
    }
    while(!S.IsEmpty()){
        sn=S.getTop();
        //棧頂結(jié)點(diǎn)的左子樹(shù)一定是已經(jīng)遍歷完了
        //因?yàn)樽笞訕?shù)上的結(jié)點(diǎn)一定在它的上面
        if(sn.p->rchild==NULL||sn.rvisited){     
            //若棧頂元素?zé)orchild或rchild已被訪問(wèn)過(guò),則出棧并訪問(wèn)它
            Pop(S, pt);
            visit(pt);
        }
        else{
            //如果有rchild且未被訪問(wèn),則去訪問(wèn)它的rchild
            sn.rvisited=1;
            pt=sn.p->rchild;
            while(pt!=NULL){
                Push(pt, 0);
                pt=pt->lchild;
           }
        }
    }
}

層次遍歷

void LevelOrder(BiTree T)
{
    InitQueue(Q);
    BiTree p;
    EnQueue(Q,T);
    while(!IsEmpty(Q)){
        DeQueue(Q,p);
        visit(p);
        if(p->lchild!=NULL)
            EnQueue(Q,p->lchild);
        if(p->rchild!=NULL)
            EnQueue(Q,p->rchild);
    }
}

由遍歷序列構(gòu)造二叉樹(shù)
由二叉樹(shù)的先序序列和中序序列可以唯一確定一棵二叉樹(shù)
由二叉樹(shù)的后序序列和中序序列可以唯一確定一棵二叉樹(shù)
由二叉樹(shù)的層序序列和中序序列可以唯一確定一棵二叉樹(shù)
由二叉樹(shù)的先序序列和后序序列不可以唯一確定一棵二叉樹(shù)

二叉樹(shù)遍歷的應(yīng)用
求二叉樹(shù)的高度
遞歸 H=max(Hl+Hr)+1

int PostOrderGetHeight(BinTree BT)
{
    int Hl,Hr,maxH;
    if(BT){
        Hl=PostOrderGetHeight(BT->left);
        Hr=PostOrderGetHeight(BT->right);
        maxH=(Hl>Hr)?Hl:Hr;
        return (maxH+1);
    }
    else    return 0;
}

二元運(yùn)算表達(dá)式及其遍歷

二元運(yùn)算表達(dá)式樹(shù).PNG

用后綴表達(dá)式構(gòu)造一棵樹(shù)
思路類似用后綴表達(dá)式求值
依次讀取,若遇到操作數(shù),則建立一個(gè)單節(jié)點(diǎn)樹(shù)并入棧,若遇到操作符,建立一個(gè)單節(jié)點(diǎn),則從棧中彈出棧頂?shù)膬煽脴?shù)T1,T2,將該節(jié)點(diǎn)作為根,T2,T1作為左右子樹(shù),再將這個(gè)樹(shù)入棧


樹(shù)和森林

**樹(shù)的存儲(chǔ)結(jié)構(gòu)
1.雙親表示法
采用一組連續(xù)空間來(lái)存儲(chǔ)每個(gè)結(jié)點(diǎn),在每個(gè)結(jié)點(diǎn)中增設(shè)一個(gè)偽指針,指示雙親結(jié)點(diǎn)在數(shù)組中的位置


樹(shù)的雙親表示法.jpg
#define MAX_TREE_SIZE 100
typedef struct{
    ElemType data;
    int parent;
}PTNode;
typedef struct{
    PTNode nodes[MAX_TREE_SIZE];
    int n;
}PTree;

可快速找到父節(jié)點(diǎn)但求子節(jié)點(diǎn)時(shí)需遍歷整個(gè)結(jié)構(gòu)

2.孩子表示法
將每個(gè)結(jié)點(diǎn)的孩子結(jié)點(diǎn)都用單鏈表鏈接起來(lái)形成一個(gè)線性結(jié)構(gòu)

3.孩子兄弟表示法
又稱二叉樹(shù)表示法
每個(gè)結(jié)點(diǎn)包括三部分:結(jié)點(diǎn)值,指向結(jié)點(diǎn)第一個(gè)孩子結(jié)點(diǎn)的指針,指向結(jié)點(diǎn)下一個(gè)兄弟結(jié)點(diǎn)的指針

typedef struct CSNode{
    ElemType data;
    struct CSNode *firstchild, *nextsibling;
}CSNode, *CSTree;
孩子表示法與孩子兄弟表示法.jpg

樹(shù),森林與二叉樹(shù)的轉(zhuǎn)換

樹(shù)轉(zhuǎn)二叉樹(shù).jpg

森林轉(zhuǎn)二叉樹(shù).jpg

樹(shù)和森林的遍歷
樹(shù)的遍歷
先根遍歷:若樹(shù)非空,則先訪問(wèn)根結(jié)點(diǎn),再按從左到右的順序遍歷根結(jié)點(diǎn)的每棵子樹(shù),其訪問(wèn)順序與這棵樹(shù)相應(yīng)二叉樹(shù)的先序遍歷順序相同。
后根遍歷:若樹(shù)非空,則按從左到右的順序遍歷根結(jié)點(diǎn)的每棵子樹(shù),之后再訪問(wèn)根結(jié)點(diǎn)。其訪問(wèn)順序與這棵樹(shù)對(duì)應(yīng)的二叉樹(shù)的中序遍歷相同。
森林的遍歷

樹(shù)和森林的遍歷.jpg


樹(shù)的應(yīng)用——并查集

有一些元素,各自為一個(gè)集合,若元素A與元素B有關(guān)系,則把它們并到同一集合,若A和C也有關(guān)系,就把C集合與之前AB合并后的集合合并。最后同一集合中的任意兩個(gè)元素都是有關(guān)系可連通的。
并查集是一種集合表示,支持以下三種操作
1.Union(S, Root1, Root2)
把集合S中的子集合Root2并入Root1中,Root1與Root2互不相交
2.Find(S, x)
查找集合S中單元素x所在的子集合
3.Initial(S)
將集合S中的每個(gè)元素初始化為只有一個(gè)單元素的子集合
通常用雙親表示法作為并查集額存儲(chǔ)結(jié)構(gòu),每個(gè)子集以一棵樹(shù)表示。
初始化時(shí)每個(gè)元素自成一個(gè)單元素子集合
用數(shù)組存儲(chǔ)

typedef struct{
    ElementType Data;
    int Parent;
}SetType;

查找某元素所在的集合

int Find(SetType S[], ElementType X)
{
    int i;
    for(i=0;i<MaxSize&&S[i].Data!=X;++i);
    if(i>=MaxSize)    return -1;  //cannot find
    for(;S[i].parent>=0;i=S[i].parent);
    return i;
}

集合的并運(yùn)算

void Union(SetType S[], ElementType X1, ElementType X2)
{
    int Root1,Root2;
    Root1=Find(S, X1);    Root2=Find(S, X2);
    if(Root1!=Root2)
        S[Root2].parent=Root1;
}

數(shù)越深Find就越耗時(shí),所以合并時(shí)盡量讓小的樹(shù)的parent指向大的樹(shù)的根,改進(jìn)方法是根節(jié)點(diǎn)的parent不再為-1而是數(shù)中元素個(gè)數(shù)的負(fù)值

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容