樹的定義

樹(Tree)是n個(n>=0)個結點的有限集。 n=0時稱之爲空樹。在任意一個非空樹中:

(1)有且僅有一個特定的稱爲根(Root)的結點;

(2)當n>1時,其餘結點可分爲m(m>0)個互不相交的有限集T1,T2,T3...Tm,其中每個集合本身就是一棵樹,並且稱爲根的子樹(Subtree)

注:n>0時,樹根僅爲1,不可能存在多個根結點;m>0時,子樹的個數沒有限制,但是他們互不相交(相交及爲圖)。


結點分類

樹的結點包含一個數據元素及若干個指向其子樹的分支。

結點擁有的子樹稱之爲結點的度。

度爲0的結點稱之爲葉子結點(Leaf)或終端結點;度不爲0的節點稱之爲非終端結點或分支結點。

除根節點外,分支結點也稱之爲內部結點。樹的度是樹內各結點度的最大值

結點間的關係

結點的子樹的根稱之爲該結點的孩子(Child),相應的,該結點稱之爲還在的雙親(Parent).

同一個雙親的孩子之間互稱兄弟(Sibling)結點的祖先是從根到該結點所經分支上所有結點。

以某一結點爲根的子樹中,任意結點都稱爲該結點的子孫。

樹的其他相關概念

結點的層次(level)從根開始定義起,根爲第一層,根的孩子爲第二層。

雙親在同一層的結點,互稱之爲堂兄弟。樹中結點最大的層次稱之爲樹的深度(Depth)或者高度。

如果將樹中結點的各個子樹看成從左向右,不能互換的,則稱該樹爲有序樹,否則爲無序樹。


森林(Forest)是m個(m>=0)棵不相交的樹的集合。對樹中每個結點而言,其子樹的集合即爲森林。


線性結構和樹形結構對比

線性結構: 第一個元素:無前驅;最後一個元素:無後繼; 中間元素:一個前驅,一個後繼

樹形結構: 根結點:無雙親,唯一; 葉子結點:無孩子,可以多個; 中間結點:一個雙親,多個孩子


樹的存儲結構

雙親表示法

假設以一組連續控件存儲樹的結點,同時在每個結點中,附設一個指示器指向其雙親結點到鏈表中的位置。

data是數據域,存儲結點的數據信息。而parent是指針域,存儲結點的雙親在數組中的下標。


結點結構定義代碼:

// 樹的雙親表示法結點定義
#define MAX_TREE_SIZE 100
typedef int TElemType;

typedef struct PTNode               //結點結構
{
    TElemType data;                 //結點數據
    int parent;                     //雙親位置
}PTNode;

typedef struct                      //樹結構
{
    PTNode node [MAX_TREE_SIZE];    //結點數組
    int r,n;                        //根的位置和結點樹
}PTree;

由於根結點沒有雙親,則約定根節點的位置域設置爲-1



對於這種存儲結構,我們可以根據結點的parent域來很容易的找到它的雙親結點,所以時間複雜度爲O(1)。直到parent爲-1爲止,表示找到了樹結點的根。

但是對於找到結點孩子是誰時,需要遍歷整個結構才行。可以通過增加域來增加這種結構的靈活性。


孩子表示法

每個結點有多個指針域,其中每個指針指向一棵子樹的根節點,這種方法叫多重鏈表表示法。


把每個結點的孩子排列起來,以單鏈表作存儲結構,則n個結點有n個孩子鏈表,如果是葉子結點,則此單鏈表爲空。然後n個頭指針又組成一個線性表,採用順序存儲結構,存放進一個一維數組中。

爲此需要準備兩種數據結構,一種是孩子鏈表的結點,其中child爲數據域,用來存儲某個結點在表頭數組中的下標。next是指針域,用來存儲指向某結點的下一個孩子結點指針。

另外一種是表頭數組的表頭結點。data爲數據域,存儲某結點的數據信息,firstchild是頭指針域,存儲該結點孩子鏈表的頭結點。


// 樹的孩子表示法結構定義

#define MAX_TREE_SIZE 100

typedef struct CTNode       //孩子結點
{
    int child;
    struct CTNode *next;
} *ChildPtr;

typedef struct              //表頭結構
{
    TElemType data;
    ChildPtr firstchild;
} CTBox;

typedef struct              // 樹結構
{
    CTBox nodes[MAX_TREE_SIZE]; //結點數組
    int r,n;                    //跟的位置和結點數
}CTree;

這種結構對於我們查找某個結點的孩子,或者某個結點的兄弟,只需要查找這個結點的孩子鏈表即可。對於遍歷整個樹也是很方便,對頭結點的數組循環即可。

但是如果要找某個結點的雙親,則需要遍歷整個樹才行。 但是可以把雙親標識法和孩子表示法綜合起來。



孩子兄弟表示法

任意一棵樹,他的結點的第一個孩子如果存在就是唯一的,他的右兄弟如果存在也是唯一的。因此,我們設置兩個指針,分別指向該結點的第一孩子和此結點的右兄弟。

data爲數據域,firstchild爲指針域,存儲結點的第一個孩子結點的存儲地址,rightsib指針域,存儲該結點的有兄弟結點存儲地址


// 樹的孩子兄弟表示法結構定義
typedef struct CSNode
{
    TElemType data;
    struct CSNode *firstchild, *rightsib;
}CSNode, *CSTree;


二叉樹的定義

二叉樹(Binary Tree) 是n(n>=0)個結點的有線集合,該集合或者爲空集(稱空二叉樹),或者由一個根結點和兩顆互不相交的,分別稱之爲根結點的左子樹和右子樹的二叉樹構成。



二叉樹特點

1,每個結點最多有兩棵子樹,所以二叉樹中不存在度大於2的結點。

2,左子樹和右子樹是有順序的,次序不能任意顛倒。

3,即使樹中某個結點只有一棵子樹,也要區分它是左子樹還是右子樹。


五種基本形態:

1,空二叉樹;

2,只有一個根結點;

3,根結點只有左子樹;

4,根節點只有右子樹;

5,根節點既有左子樹又有右子樹。


對於三個結點的樹,若只考慮形態,只有兩種狀況,但是二叉樹是區分左右的,所以就演變成五種形態。


特殊二叉樹

1,斜樹

所有的結點只有左子樹的二叉樹叫做左斜樹,所有結點只有右子樹的二叉樹叫做右斜樹。

特點,每層只有一個結點,結點的個數與二叉樹的深度相同


2,滿二叉樹

在一棵二叉樹中,如果所有的分支都存在左子樹和右子樹,並且所有葉子都在同一層上,這樣的二叉樹稱爲滿二叉樹。


單是每個結點都存在左右子樹,不能算是滿二叉樹,還必須要所有的葉子結點都在同一層上,這樣做到了整棵樹的平衡。


滿二叉樹的特點:

1,葉子只能出現在最下面一層。

2,非葉子結點的度一定是2。

3,在同樣深度的二叉樹中,滿二叉樹的結點個數最多,葉子數最多。


3,完全二叉樹

對一棵具有n個結點的二叉樹按層序編號,如果編號i (1<=i<=n)的結點與同樣深度的滿二叉樹序號爲i的結點在二叉樹中的位置完全相同,則這個二叉樹爲完全二叉樹。


滿二叉樹一定是一棵完全二叉樹,但是完全二叉樹不一定是滿二叉樹。

完全二叉樹的所有結點與同樣深度的滿二叉樹,他們按層序號的結點,是一一對應的。


完全二叉樹的一些特點:(判斷方法)

1,葉子結點只能出現在最下面兩層;

2,最下層的葉子一定集中在左邊連續位置;

3,倒數兩層,若有葉子結點,一定都在右部連續位置;

4,如果結點度爲1,則該結點只有左孩子,不可能存在只有右子樹的情況;

5,同樣結點樹的二叉樹,完全二叉樹的深度最小。


二叉樹的性質

二叉樹性質1

性質1: 在二叉樹的第i層上至多有2^(i-1)個結點。(i>=1)


二叉樹性質2

性質2:深度爲k的二叉樹至多有2^k-1個結點(k>=1)

注:是2^k -1


二叉樹性質3

性質3:對任何一棵二叉樹T, 如果其終端結點(葉子結點)數爲n0, 度爲2的結點數爲n2,則 n0 = n2 +1;


二叉樹性質4

性質4:具有n個結點的完全二叉樹的深度爲[log2n] + 1.


二叉樹性質5

性質5:如果對一棵有n個結點的完全二叉樹(其深度爲[log2n] + 1)的結點按層序號,對任一結點i (1<= i<=n)有:

1,如果i =1, 則結點i是二叉樹的根,無雙親;如果i>1, 則其雙親是結點[i/2];

2,如果2i > n, 則結點i 無左孩子(結點i爲葉子結點);否則其左孩子是結點2i;

3,如果2i + 1 > n,則結點 i無右孩子; 否則其有孩子是結點 2i+1;



二叉樹的存儲結構

二叉樹順序存儲結構

二叉樹的順序存儲結構就是用一維數組存儲二叉樹中的結點,並且結點的存儲位置,也就是數組的下標需要體現結點間的邏輯關係。


這只是對於完全二叉樹,但是對於一般二叉樹,儘管層序編號不能反映邏輯關係,但是可以將其按完全二叉樹編號,只不過,把不存在的幾點設置爲”/\“。


對於極端情況,一棵深度爲K的右斜樹,他只有K個幾點,卻要分配2^k -1個存儲單元的空間。這顯然是對存儲空間的浪費。 所以對於順序存儲,一般只用在完全二叉樹結構。




二叉鏈表

二叉樹每個結點最多有兩個孩子,所以爲他涉及一個數據域和兩個指針域,稱這樣的鏈表叫做二叉鏈表

其中data是數據域,lchild 和 rchild 都是指針域。分別存放指向左孩子和有孩子的結點。

// 二叉樹的二叉鏈表結點結構定義
typedef struct BiTNode
{
    TELemType data;
    struct BiTNode *lchild,*rchild;
}BiTNode, *BiTree;

遍歷二叉樹

二叉樹遍歷原理

二叉樹的遍歷,是指從根結點出發,按照某種次序依次訪問二叉樹中所有結點,使得每個結點被訪問一次且僅僅被訪問一次。


二叉樹遍歷方法:

二叉樹的遍歷方式很多:

1,前序遍歷:

規則是若二叉樹爲空,則空操作返回,否則先訪問根結點,然後前序遍歷左子樹,再前序遍歷右子樹。


2,中序遍歷

若樹爲空,則空操作返回,否則從根節點開始(並不是先訪問根節點),中序遍歷根節點的左子樹,然後訪問根節點,最後中序遍歷右子樹。


3,後序遍歷

若樹爲空,則空操作返回,否則從左到右先葉子後根結點的方式遍歷訪問左右子樹,最後是訪問根結點。


4,層序遍歷

若樹爲空,則空操作返回,否則從樹的第一層,也就是根結點開始訪問,從上而下逐層遍歷,在同一層中,按從左到右的順序對結點逐個訪問。


前序遍歷算法

二叉樹定義是用遞歸的方式,所以,實現遍歷算法也可以採用遞歸方式

// 二叉樹的先序遍歷算法

void PreOrderTraverse(BiTree T)
{
    if(T == NULL)
        return;

    printf("%c",T->data);   //顯示結點數據

    PreOrderTraverse(T->lchild);    //先序遍歷左子樹
    PreOrderTraverse(T->rchild);    //最後先序遍歷右子樹
}

非遞歸方式:

void PreOrderTraverseNo(BiTree T)
{
    BiTree p,q;
    *p = T;
    int top;       // 棧頂索引

    BiTree stack[MAX_SIZE] = {0};   //初始化棧


    if(T == NULL)
    {
        return NULL:
    }
    else
    {
        while(p != NULL)            
        {
            cout<< p->data;         //訪問結點

            q = p->rchild;          //取其右子樹結點

            if(q != NULL)           //不空,壓棧
                stack[++top] = q;

            p = p->lchild;          //遍歷左子樹

            if(p == NULL)           //爲空彈棧,找右子樹
                p = stack[top--];
        }
    }
}


中序遍歷算法

// 二叉樹的中序遍歷算法

void InOrderTraverse(BiTree T)
{
    if(T == NULL)
        return;

    InOrderTraverse(T->lchild); //中序遍歷左子樹
    printf("%c",T->data);       // 顯示結點數據,可以更改爲其他的對結點操作
    InOrderTraverse(T->rchild); //最後中序遍歷右子樹
}

後序遍歷算法

// 二叉樹的後序遍歷算法

void PostOrderTraverse(BiTree T)
{
    if(T == NULL)
        return;

    PostOrderTraverse(T->lchild); //後序遍歷左子樹    
    PostOrderTraverse(T->rchild); //後序遍歷右子樹
    printf("%c",T->data);       // 顯示結點數據,可以更改爲其他的對結點操作
}

二叉樹遍歷的性質:

已知前序遍歷序列中序遍歷序列,可以唯一確定一棵二叉樹。

已知後續遍歷序列和中序遍歷序列,可以唯一確定一棵二叉樹。


但是,已知前序和後序遍歷,是不能確定一棵二叉樹的。


二叉樹的建立

爲了簡歷一棵樹,爲了讓每個結點確認是否有左右孩子,我們對他進行了擴展,也就是將二叉樹中每個結點的空指針引出一個虛結點,其值爲一個特定值,如”#“。這種處理後的二叉樹叫做原二叉樹的擴展二叉樹。擴展二叉樹就可以做到一個遍歷確定一棵二叉樹,如下樹的前序遍歷爲:AB#D##C##



// 按前序輸入二叉樹中的結點值,#標識空樹,構造二叉鏈表表示的二叉樹

void CreatBiTree(BiTree* T)
{
    TElemType ch;
    scanf("%c",&ch);

    if(cd == '#')
        *T = NULL;
    else
    {
        *T = (BiTree)malloc(sizeof(BiTree));
        if(!*T)
        {
            exit(OVERFLOW);
        }

        (*T)->data = ch; //生成根結點
        CreatBiTree(&(*T)->lchild); //構造左子樹
        CreatBiTree(&(*T)->rchild); //構造右子樹
    }

}

可看到構造子樹時是用到了先序方式,也可以根據需要用中序或者後序方式構造。


線索二叉樹(未完)



發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章