數據結構復習-樹與二叉樹(1)

樹的概念與基本術語

樹是若干結點的集合,是由唯一的根和若干棵互不相交的子樹組成的。樹的概念是遞歸的,即在樹的定義中又用到了樹的定義。

結點的度:結點擁有的子樹個數或者分支的個數。
樹的度:樹中各結點度的最大值。
層次:從根開始,根為第一層,根的孩子為第二層。
樹的高度(或者深度):樹中結點的最大層次。
結點的深度:從根結點到該結點路徑上的結點個數
結點的高度:從某結點往下走可能到達多個葉子結點,對應了多條通往這些葉子結點的路徑,其中最長的那條路徑的長度即為該結點在樹中的高度。根結點的高度為樹的高度。
兄弟:同一個雙親的孩子之間互為兄弟。
堂兄弟:雙親在同一層的結點互為堂兄弟。
有序樹與無序樹:樹中的結點的子樹從左到右是有次序的,不能交換,這樣的樹叫做有序樹。可以隨便交換的叫做無序樹。
豐滿樹:豐滿樹即理想平衡樹,要求除最底層外,其他層都是滿的。
森林:若干棵互不相交的樹的集合。

樹的存儲結構:

1.順序存儲結構:
樹的順序存儲結構中最簡單直觀的是雙親存儲結構。用一個整數數組來存儲,下標就是該結點,數組元素的內容表示該結點的雙親結點。例如3的雙親是1,則tree[3]=1.
樹的雙親存儲結構在克魯斯卡爾算法中有重要應用。

2.鏈式存儲結構:
1)孩子存儲結構:孩子存儲結構實質上就是圖的鄰接表存儲結構。樹就是一種特殊的圖。
2)孩子兄弟存儲結構:孩子兄弟存儲結構與樹和森林與二叉樹的相互轉換關系密切。

二叉樹

二叉樹滿足以下定義:

  1. 每個結點最多只有兩棵子樹,即二叉樹中結點的度只能為0、1、2
  2. 子樹有左右順序之分,不能顛倒

滿二叉樹:如果所有的分支結點都有左孩子和右孩子結點,并且葉子結點都集中在二叉樹的最下一層,則這樣的二叉樹成為滿二叉樹。
完全二叉樹:如果對一棵深度為k,有n個結點的二叉樹進行編號后,各結點的編號與深度為k的滿二叉樹中相對位置上的結點的編號均相同,那么這棵樹就是一棵完全二叉樹。
一棵完全二叉樹一定是由一棵滿二叉樹從右到左,從下到上,挨個刪除結點所得到的。如果跳著刪除,則得到的不是完全二叉樹。

二叉樹的主要性質:

性質1. 非空二叉樹上葉子結點數等于雙分支結點數加1.
證明:假設葉子結點數為n0,單分支結點數為n1,雙分支結點數為n2,則總結點數為n0+n1+n2。總分支數為n1+2n2。由于除了根結點外,其它結點都有一個分支指向它,所以總分支數=總結點數-1。得到n1+2n2 = n0 + n1 + n2 - 1。得到:n2 = n0 - 1。

一個變形:問二叉樹中總的結點數為n,則樹中空指針數為多少?可以將空指針數看成是葉子結點,則每個結點都是雙分支結點。根據性質1,葉子結點數等于雙分支結點數加1,則空指針數=n+1.

性質2. 二叉樹的第i層最多有2^(i-1)個結點(i>=1).
結點最多的情況即為滿二叉樹的情況,此時二叉樹每層上的結點數構成了一個首項為1,公比為2的等比數列。通向為2^(i-1),i為層號。

性質3. 高度(或深度)為k的二叉樹最多有2^k - 1個結點.
最多的情況還是滿二叉樹,則根據等比公式求和可得(1-2k)/(1-2)=2k - 1.
在這里復習一下等比數列求和公式和等差數列求和公式:

等比數列求和公式.png

等差數列求和公式.png

性質4. 有n個結點的完全二叉樹,對各結點從上到下,從左到右依次編號,則結點之間有如下關系:(i為某結點a的編號)

  1. 如果i不等于1,則a的雙親結點的編號為i/2向下取整
  2. 如果2i<=n,則a的左孩子的編號為2i;如果2i > n,則a無左孩子
  3. 如果2i+1 <= n,則a的右孩子編號為2i+1;如果2i+1 > n,則a無右孩子

性質5. 函數catalan( ):給定n個結點,能構成h(n)種不同的二叉樹,h(n)=C(2n,n)/(n+1)

性質6. 具有n個結點的完全二叉樹的高度或深度為 (log2n向下取整+1),或者(log2(n+1)向上取整)

二叉樹的存儲結構

1. 順序存儲結構:
順序存儲結構即用一個數組來存儲一棵二叉樹,這種存儲方式最適合于完全二叉樹,用于存儲一般的二叉樹會浪費大量存儲空間
將完全二叉樹中的結點值按編號依次存入一個一維數組中,即完成了一棵二叉樹的順序存儲。如果知道了一個結點的下標為i,則該結點的左孩子的下標為2i,右孩子為2i+1(當其存在時)。

2. 鏈式存儲結構(二叉鏈表存儲結構):

typedef struct BTNode {
    char data; //數據域,可更改為其他類型
    struct BTNode *lchild;
    struct BTNode *rchild;
};

二叉樹的遍歷算法

二叉樹的遍歷方式有先序遍歷、中序遍歷、后序遍歷和層次遍歷

1.先序遍歷preorder

根、左、右

void preorder(BTNode *p) {
    if (p != NULL) {
        visit(p); //對該結點的訪問操作,例如打印該結點的數值等信息
        preorder(p->lchild);
        preorder(p->rchild);
    }
}

2.中序遍歷inorder

左、根、右

void inorder(BTNode *p) {
    if (p != NULL) {
        inorder(p->lchild);
        visit(p);
        inorder(p->rchild);
    }
}

3.后序遍歷postorder

左、右、根

void postorder(BTNode *p) {
    if (p != NULL) {
        postorder(p->lchild);
        postorder(p->rchild);
        visit(p);
    }
}

典型例題:寫一個算法求一棵二叉樹的深度,二叉樹以二叉鏈表為存儲方式。

int getDepth(BTNode *p) {
  int LD, RD;
  if (p == NULL)
    return 0;
   else {
        LD =  getDepth(p->lchild);
        RD = getDepth(p->rchild);
        return (LD>RD?LD:RD)+1;//返回左右子樹深度的最大值加1
  }
}

一個結論:根據二叉樹的前、中、后3種遍歷序列中的前和中、中和后兩對遍歷序列都可以唯一確定這棵二叉樹,而根據前和后這對遍歷序列不能確定這棵二叉樹。

例題:假設二叉樹采用二叉鏈表存儲結構存儲,輸出先序遍歷序列中的第k個結點的值,假設k不大于總的結點數。

int n;
void print_k_of_preorder(BTNode *p, int k) {
    if (p != NULL) {
        ++n;
        if (k == n) {
            cout<<p->data<<endl;
            return;
        }
        print_k_of_preorder(p->lchild, k);
        print_k_of_preorder(p->rchild, k);
    }
}

4.層次遍歷level

對二叉樹的層次遍歷即從上到下,從左到右的遍歷結點。
對二叉樹的層次遍歷,需要建立一個循環隊列,先將二叉樹頭結點入隊列,然后出隊列,訪問該結點,如果它有左子樹,則將左子樹的根結點入隊;如果它有右子樹,則將右子樹的根結點入隊。然后出隊列,對出隊結點訪問,如此反復,直到隊列為空為止。

void level(BTNode *p) {
    queue<BTNode *> queue;
    BTNode *q;
    if (p != NULL)
        queue.push(p);

    while (!queue.empty( )) {
        q = queue.front( );
        queue.pop( );
        cout<<q->data<<endl;
        if (q->lchild != NULL)  
            queue.push(q->lchild);
        if (q->rchild != NULL)
            queue.push(q->rchild);
    }
}

二叉樹遍歷算法的改進

之前的幾個二叉樹的深度優先(DFS)遍歷算法,都是用遞歸實現的,這是很低效的。因為遞歸調用了系統本身的棧,會有很大開銷。用戶自己實現非遞歸的算法比較高效。

1. 先序遍歷的非遞歸實現preorder

根結點入棧。當棧不為空時,出棧棧頂元素,將其右結點入棧,左結點入棧...

void PreOrderNonRecursion(BTNode *root) {
  stack<BTNode *> node;
  BTNode *p = root;
  if (p != NULL) {
    node.push(p);

    BTNode *q;
    while (!node.empty()) {
      q = node.top();
      node.pop();
      cout<<q->val<<endl;
      if (q->rchild)
        node.push(q->rchild);
      if (q->lchild)
        node.push(q->lchild);
    }
  }
}

2. 中序遍歷的非遞歸實現inorder

void InOrderNonRecursion(BTNode *root) {
  stack<BTNode *> node;
  BTNode *p, *q;
  p = root;

  while (!node.empty() || p != NULL) {
    while(p != NULL) {
      node.push(p);
      p = p->lchild;
    }

    if (!node.empty()) {
      p = node.top();
      node.pop();
      cout<<p->val<<endl;
      p = p->rchild;
    }
  }
}

3. 后序遍歷的非遞歸實現postorder

vector<int> PostOrderNonCursivion(BTNode *root) {
  vector<int> postSeq;
  stack<BTNode *> s;

  if (root == NULL)
    return postSeq;

  BTNode *cur;
  s.push(root);
  BTNode *pre = NULL;

  while (!s.empty()) {
    cur = s.top();
    if ((cur->left == NULL && cur->right == NULL) || (pre != NULL) && (pre == cur->left || pre->cur->right)){
      /* 如果這個結點是葉子結點,或該結點的左右孩子被訪問過了,則可訪問它 */
      postSeq.push_back(cur->val);
      pre = cur;
      s.pop();
    }
    else {
      if (cur->right != NULL) {
        s.push(cur->right);
      }
      if (cur->left != NULL) {
        s.push(cur->left);
      }
    }
  }
}

線索二叉樹

二叉樹非遞歸遍歷算法避免了系統棧的調用,提高了一定的執行效率。線索二叉樹可以將用戶棧也省掉,把二叉樹的遍歷過程線性化,進一步提高效率。
n個結點的二叉樹有n+1個空鏈域,線索二叉樹將這些空鏈域利用起來。
二叉樹被線索化后近似于一個線性結構,分支結構的遍歷操作就轉化為了近似于線性結構的遍歷操作,通過線索的輔助使得尋找當前結點前驅或者后繼的平均效率大大提高。
線索二叉樹的結點定義如下:

typedef struct TBTNode {
    char data;
    int ltag, rtag; //線索標記
    struct TBTNode *lchild;
    struct TBTNode *rchild;
};

其中的兩個線索標記,如果ltag=0,則表示lchild為指針,指向結點的左孩子;如果ltag=1,則表示lchild為線索,指向結點的直接前驅
如果rtag=1,則表示rchild為線索,指向結點的直接后繼

線索二叉樹可以分為前序線索二叉樹、中序線索二叉樹、后序線索二叉樹。對一棵二叉樹中所有結點的空指針域按照某種遍歷方式加線索的過程叫做線索化,被線索化了的二叉樹稱為線索二叉樹。
中序線索二叉樹的考察最頻繁。中序線索化的規則是,左線索指針指向當前結點在中序遍歷序列中的前驅結點,右線索指針指向后繼結點。

void InThread(TBTNode *p, TBTNode *&pre) {
  if (p != NULL) {
    InThread(p->lchild, pre);
    if (p->lchild == NULL) {
      p->lchild = pre;
      p->ltag = 1;
    }
    if (pre != NULL && pre->rchild == NULL) {
      pre->rchild = p;
      pre->rtag = 1;
    }
    pre = p;
    InThread(p->rchild, pre);
  }
}

通過中序遍歷建立中序線索二叉樹的主程序如下:

void createInThread(TBTNode *root) {
  TBTNode *pre = NULL;
  if (root != NULL) {
    InThread(root, pre);
    pre->rchild = NULL;
    pre->rtag = 1;
  }  
}

二叉樹的應用

1. 二叉排序樹和平衡二叉樹
2. 赫夫曼樹和赫夫曼編碼

赫夫曼樹

赫夫曼樹又叫做最優二叉樹,它的特點是帶權路徑最短。
樹的帶權路徑長度WPL:所有葉子結點的帶權路徑長度(從該結點到根之間的路徑長度乘以結點的權值)之和。

赫夫曼樹的構造方法

1)將這n個權值分別看作只有根結點的n棵二叉樹,這些二叉樹構成的集合記為F
2)從F中選取兩棵根結點的權值最小的樹作為左右子樹,構造一棵新的二叉樹,新的二叉樹的根結點的權值為左右子樹根結點權值之和。
3)重復進行,直到構造成一棵二叉樹。

赫夫曼編碼

在存儲文件的時候,對于包含同一內容的文件有多種存儲方式,可以找出一種最節省空間的存儲技術。這就是赫夫曼編碼的用途。常見的.zip壓縮文件和.jpeg圖片文件的底層技術都用到了赫夫曼編碼。
按字符出現的次數為權值,構造赫夫曼樹,再進行前綴編碼。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,461評論 6 532
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,538評論 3 417
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,423評論 0 375
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,991評論 1 312
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,761評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,207評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,268評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,419評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,959評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,782評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,983評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,528評論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,222評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,653評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,901評論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,678評論 3 392
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,978評論 2 374

推薦閱讀更多精彩內容

  • 一些概念 數據結構就是研究數據的邏輯結構和物理結構以及它們之間相互關系,并對這種結構定義相應的運算,而且確保經過這...
    Winterfell_Z閱讀 5,882評論 0 13
  • 1.樹(Tree): 樹是 n(n>=0) 個結點的有限集。當 n=0 時稱為空樹。在任意一顆非空樹中:有且僅有一...
    ql2012jz閱讀 1,032評論 0 3
  • 數據結構和算法--二叉樹的實現 幾種二叉樹 1、二叉樹 和普通的樹相比,二叉樹有如下特點: 每個結點最多只有兩棵子...
    sunhaiyu閱讀 6,490評論 0 14
  • 前面講到的順序表、棧和隊列都是一對一的線性結構,這節講一對多的線性結構——樹。「一對多」就是指一個元素只能有一個前...
    Alent閱讀 2,262評論 1 28
  • 第一章 緒論 什么是數據結構? 數據結構的定義:數據結構是相互之間存在一種或多種特定關系的數據元素的集合。 第二章...
    SeanCheney閱讀 5,796評論 0 19