數據結構(16)樹與森林

前言

樹是n(n>=0)個結點的有限集。在任意一棵非空樹中:1.有且只有一個特定的稱爲根的結點 2.當n>1時,其餘結點可分爲m(m>=0)個互不相交的有限集,並且稱爲根的子樹。可以發現,在樹的定義中,又用到了樹的概念,樹的結構定義是一個遞歸的定義。

森林則是m(m>=0)棵互不相交的樹的集合。對樹中的每一個結點而言,其子樹的集合即爲森林。因此,也可以用森林和樹相互遞歸的定義來描述樹。

樹的應用很廣泛,但是由於樹的形狀可以任意,顯然對樹的處理會複雜許多。

樹的存儲結構

雙親表示法

在雙親表示法中,結點擁有一個數據域和一個指針域,其中指針域指向結點的雙親結點。

(注:”雙親“結點即父結點,並不意味着兩個有結點,搞不懂爲啥要這樣翻譯)

這裏我們以順序存儲爲例子,假設用一個數組來保存整棵樹,那麼數組下標就是結點的位置。

img_1

如果用這種方式,那麼結點的結構如下:

typedef struct PTNode{
  //數據域
  ElemType data;
  //指針域
  int parent;
}PTNode;

使用這種表示法的缺點也很明顯:假如想得到結點的孩子,需要遍歷整個結構才能得到。

孩子表示法

顧名思義,孩子表示法即指針域指向的是結點的孩子結點。但是與二叉樹不同,一棵樹可以擁有多個結點,那麼指針域該如何設計呢?

第一種方法是採用固定指針域數量的結點,也就是說結點的格式是一致的。

img_2

typedef struct CTNode{
  //數據域
  ElemType data;
  //指針域
  int child1;
  int child2;
  //...
  int childd;
}CTNode;

這種方式的弊端很明顯:假設某個結點的孩子結點數量很多,而其他結點的孩子結點數量很少時,由於採取了統一格式,所有結點的指針域數量必須同最大的數量一致,這樣就造成了浪費。

因此可以採取第二種方式:增加一個數據域用於記錄結點的度,根據度來確定指針域的個數。

img_3

typedef struct CTNode{
  //數據域
  ElemType data;
  //結點的度
  int degree;
  //指針域
  int child1;
  //...
}CTNode;

顯然,這種方法雖然能節約空間,但是每個結點的結構不一樣,操作上很不方便。

有一種改進方法是,將每個結點的孩子結點排列起來,看成一個線性表,以單鏈表作爲存儲結構。

img_4

這樣,我們需要設計一個孩子結點,作爲該鏈表存儲的對象。

//孩子結點
typedef struct ChildNode{
  //孩子結點的位置
  int child;
  //指向下一個孩子結點
  struct ChildNode* next;
}ChildNode;

樹的結點:

//樹的結點
typedef struct CTNode{
  //數據域
  ElemType data;
  //指針域-孩子結點
  ChildNode* firstChild;
}CTNode;

採用這種方式很容易找到孩子結點,但是不容易找到父結點。可以與雙親表示法結合起來,使操作更簡便。

孩子兄弟表示法

孩子兄弟表示法又稱爲二叉樹表示法,即用二叉鏈表作爲樹的存儲結構。這樣,樹的結點一定有兩個指針域,規定將左結點的指針域指向該結點的第一個孩子結點,右結點的指針域指向結點的下一個兄弟結點,得到孩子兄弟表示法的結點結構如下:

typedef struct CSNode{
  //數據域
  ElemType data;
  //指針域-左結點指向第一個孩子結點
  struct CSNode* firstChild;
  //指針域-右結點指向下一個兄弟結點
  struct CSNode* nextSibling;
}CSNode;

用孩子兄弟表示法來表示樹,可以看做是將一棵樹轉換爲二叉樹。

樹、森林與二叉樹的轉換

樹轉換爲二叉樹

前面提到,孩子兄弟表示法即使用二叉鏈表來存儲樹。

作爲二叉鏈表,除了數據域外,它必定只有兩個指針域,指向其左結點和右結點。

在二叉樹中,左結點指向的是左子樹,右結點指向的是右子樹。

在線索二叉樹中,左結點指向左子樹或者前驅,右結點指向右子樹或後繼。

在樹中,沒有左右子樹的說法,那麼用左結點指向它的第一個孩子結點,右結點指向它的下一個兄弟結點。

示例:

img_5

  • 首先存儲R,其第一個孩子結點爲A,則R的左結點爲A;R無兄弟結點,右結點爲空
  • A的第一個孩子結點爲D,則A的左結點爲D;A的下一個兄弟節點爲B,則其右結點爲B
  • D無孩子結點,則其左結點爲空;D的下一個兄弟結點爲E,則其右結點爲E
  • E無孩子結點,則其左結點爲空;E無下一個兄弟,則其右結點也爲空
  • B無孩子結點,則其左結點爲空;B的下一個兄弟結點爲C,則其右結點爲C
  • C的第一個孩子結點爲F,則其左結點爲F;C無下一個兄弟,則其右結點爲空
  • F的第一個孩子結點爲G,則其左結點爲G;F無兄弟,則其右結點爲空
  • G無孩子結點,則其左結點爲空;G的下一個兄弟結點爲H,則其右結點爲H
  • H無孩子結點,則其左結點爲空;H的下一個兄弟結點爲K,則其右結點爲K
  • K無孩子結點,則其左結點爲空;K無下一個兄弟,則其右孩子爲空

img_6

由此可見,在物理結構上,二叉樹與孩子兄弟表示法的樹是一致的,只是解釋方法不同,含義就不同了。

由於根結點沒有兄弟,因此根結點的右結點必爲空。

森林轉換爲二叉樹

森林是由若干棵樹組成的,因此每一棵樹都能各自轉換爲二叉樹。同時可以認爲,森林中的每一棵樹都是兄弟,即根結點的兄弟結點是下一棵樹的根結點。基於這兩點,森林轉換爲二叉樹是很容易的。

img_7

首先,將第一棵樹轉換爲二叉樹。

  • A爲根結點,第一個孩子結點爲B,則其左結點爲B;下一棵樹的根結點爲E,則其右結點爲E
  • B無孩子結點,則其左結點爲空;下一個兄弟結點爲C,則其右結點爲C
  • C無孩子結點,則其左結點爲空;下一個兄弟結點爲D,則其右結點爲D
  • D無孩子結點,無下一個兄弟結點,左右結點均爲空

img_8

然後轉換第二棵樹

  • E爲根結點,第一個孩子結點爲F,則其左結點爲F;下一棵樹的根結點爲G,則其右結點爲G
  • F無孩子結點,無下一個兄弟結點,左右結點均爲空

img_9

轉換第三棵樹

  • G爲根結點,第一個孩子結點爲H,則其左結點爲H;無下一棵樹,則其右結點爲空
  • H無孩子結點,則其左結點爲空;下一個兄弟結點爲I,則其右結點爲I
  • I第一個孩子結點爲J,則其左結點爲J;無下一個兄弟結點,則其右結點爲空
  • J無孩子結點,無下一個兄弟結點,左右結點均爲空

img_10

二叉樹也可以再次轉換爲樹或森林,只要知道其原理,逆過來做很容易,就不說明了。

孩子兄弟樹的實現

由於孩子兄弟樹實際上就是一顆二叉樹,其實現與二叉樹並沒有太大的差別,這裏就不一一解釋了。需要注意的是,由於右結點不再是結點的子樹而是結點的兄弟,在進行一些操作時需要格外注意。

以尋找結點的父結點爲例。

img_6

假如按照二叉樹的方法,查找結點B的父結點,得到的是結點A,這顯然是錯誤的。

全部代碼

Tree.hpp

#ifndef Tree_hpp
#define Tree_hpp

#include <stdio.h>
#include <stdlib.h>
#include <assert.h>

#define ElemType char

typedef struct TreeNode{
    ElemType data;
    struct TreeNode *FirstChild;
    struct TreeNode *nextSibling;
}TreeNode;

typedef struct Tree{
    TreeNode * root;
    ElemType refValue;
}Tree;

//初始化
void InitTree(Tree *tree,ElemType ref);
//創建
void CreateTree(Tree *tree,char *str);
void CreateTree(Tree *tree,TreeNode *&t,char *&str);

//得到根結點
TreeNode* Root(Tree *tree);

//得到第一個孩子
TreeNode* FirstChild(Tree *tree);
TreeNode* FirstChild(TreeNode *&t);

//得到第一個兄弟
TreeNode* NextSibling(Tree *tree);
TreeNode* NextSibling(TreeNode *&t);

//查找某個結點
TreeNode* Find(Tree *tree,ElemType key);
TreeNode* Find(TreeNode *&t,ElemType key);

//查找某個結點的父結點
TreeNode* Parent(Tree *tree,TreeNode *p);
TreeNode* Parent(TreeNode *&t,TreeNode *p);

#endif /* Tree_hpp */

Tree.cpp

#include "Tree.hpp"

void InitTree(Tree *tree, ElemType ref){
    tree->root = NULL;
    tree->refValue = ref;
}

//創建
void CreateTree(Tree *tree,char *str){
    CreateTree(tree,tree->root, str);
}
void CreateTree(Tree *tree,TreeNode *&t,char *&str){
    if (*str == tree->refValue) {
        t = NULL;
    }else{
        t = (TreeNode *)malloc(sizeof(TreeNode));
        assert(t != NULL);
        t->data = *str;
        CreateTree(tree, t->FirstChild, ++str);
        CreateTree(tree, t->nextSibling, ++str);
    }
}

//
TreeNode* Root(Tree *tree){
    return tree->root;
}

//得到第一個孩子
TreeNode* FirstChild(Tree *tree){
   return FirstChild(tree->root);
}
TreeNode* FirstChild(TreeNode *&t){
   return t == NULL ? t : t->FirstChild;
}

//得到第一個兄弟
TreeNode* NextSibling(Tree *tree){
    return NextSibling(tree->root);
}
TreeNode* NextSibling(TreeNode *&t){
    return t == NULL ? t : t->nextSibling;
}

//查找某個結點
TreeNode* Find(Tree *tree,ElemType key){
    return Find(tree->root, key);
}
TreeNode* Find(TreeNode *&t,ElemType key){
    //判斷是否是本結點
    if (t == NULL || t->data == key) {
        return t;
    }
    
    //不是,先在孩子結點中查找
    TreeNode *p = Find(t->FirstChild, key);
    if (p == NULL) {
        //子樹中沒有,在兄弟結點中查找
        p = Find(t->nextSibling, key);
    }
    return p;
}

TreeNode* Parent(Tree *tree,TreeNode *p){
    return Parent(tree->root, p);
}
TreeNode* Parent(TreeNode *&t,TreeNode *p){
    if (t == NULL || p == NULL || p == t) {
        return NULL;
    }
    
    TreeNode *q = t->FirstChild;
    TreeNode *parent;
    while (q != NULL && q != p) {
        //先在孩子結點中查找
        parent = Parent(q,p);
        if(parent != NULL){
            return parent;
        }
        //孩子結點中沒有,在兄弟結點中查找
        q = q->nextSibling;
    }
    
    if (q != NULL && q == p) {
        return t;
    }
    return NULL;
}

main.cpp

#include "Tree.hpp"

int main(int argc, const char * argv[]) {
    
    char *str = "RAD#E##B#CFG#H#K#####";
    
    Tree tree;
    InitTree(&tree, '#');
    CreateTree(&tree,str);
    
    
    TreeNode *p = Find(&tree, 'H');
    printf("%c\n",p->data);
    
    TreeNode *parent = Parent(&tree, p);
    printf("%c\n",parent->data);
    return 0;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章