[數據結構]二叉樹創建與遍歷


實驗報告:二叉樹創建與遍歷

一、問題描述

二叉樹是一種實用范圍很廣的非線性結構,一棵非空二叉樹有也只有一個根結點,每個結點最多有兩個子樹,我們稱為左子樹與右子樹,當一個結點的左、右子樹都是空的時,沃恩稱此結點為葉子結點。

二叉樹有一些很好的性質,這里不再贅述。考慮如何存儲一棵樹,本實驗選擇使用鏈式存儲結構——二叉鏈表;如果事先知道需要存儲的二叉樹是滿二叉樹或者完全二叉樹,則可以考慮使用順序存儲,否則將浪費大量的存儲空間。

對於一棵既成的二叉樹,有三種遍歷方式——先序、中序與后序。可以證明,一棵形態固定的二叉樹與先序遍歷、后序遍歷生成的序列是1-1對應的,基於這一點,我們可以從先序序列或后序序列出發得到唯一與之對應的二叉樹。這是本實驗創建二叉樹的理論基礎。

值得注意的是,一個中序遍歷序列與二叉樹並不是1-1對應的關系,也就是說一個中序序列對應的二叉樹的形態並不固定,即使像本實驗中這樣添加了虛空結點也不是1-1對應的,所以中序列並不能夠生成一棵二叉樹。

本實驗主要給出先序創建二叉樹,以及中序遍歷該樹。

二、數據結構——二叉鏈表

二叉鏈表是二叉樹的鏈式存儲結構,其結構與二叉樹的性質有天然的契合。

我們知道,二叉樹的結點需要保存的信息有:當前結點的信息、左子樹、右子樹。基於如此的結構,我們在一個結點處分為數據域和指針域:數據域保存該結點自身的信息;指針域分為左、右指針域,分別指向該結點的左、右子樹;有必要時可以加上一個鏈域指向該結點的父親結點(雙親結點),當然這樣就成了三叉鏈表。三叉鏈表與二叉鏈表沒有太大的差別,只是三叉鏈表在具體操作涉及到尋找結點的祖先時會有優勢,比如尋找最近公共祖先(lca),這與本實驗無關,不再贅述。

值得注意的是,當結點的某個子樹不存在時,指向該結點的指針域應該是NULL的。有個小技巧是,可以人為定義一個“空指針域”,應該指向NULL的時候都指向這個所謂的“空結點”,可以一定程度上避免指針訪問越界的錯誤。

三、算法的設計和實現

這個算法的思想非常簡單。本實驗可以分為兩部分——創建二叉樹與遍歷二叉樹。

1、創建二叉樹(以先序序列為例)

樹的序列生成是一個遞歸的過程,所以以遍歷序列創建樹的時候也需要遞歸生成這棵樹。

以先序序列為例,如果當前標號非空,那么我們得到的就是當前結點的數據,保存該數據后,下一個數據——無論是虛空結點還是真實的數據——是左子樹的數據,所以我們將遞歸到左子樹中;如果當前標號是空的,即虛空結點,表示此處不存在結點,返回NULL即可;當前結點的左子樹遞歸返回后進入右子樹遞歸;返回該結點的地址,結束。

可以看出這就是一個模擬生成先序序列的過程,着眼於一個結點來說,就是先訪問當前子樹的根節點,然后左子樹,再右子樹,最后返回。

后序序列也可以類似地生成,具體細節將在下面提到。

2、遍歷二叉樹(以中序遍歷為例)

與樹的創建相似,二叉樹的遍歷同樣是一個遞歸的過程。

以中序序遍歷為例,如果左子樹非空,則遞歸進入左子樹;輸出當前結點的標號;如果右子樹非空,則遞歸進入右子樹;結束。如果不是需要輸出序列,而是想得到這個序列,那么很自然地可以想到棧,將輸出標號的操作改為將標號壓棧即可,別的操作不改動。

遍歷二叉樹的意義重大,比如中序遍歷一棵排序二叉樹,得到的就是保存的有序的序列。

四、預期結果和實驗中的問題

1、預期結果:

程序能夠根據輸入的先序序列(帶有虛空結點),正確地生成對應的二叉樹,並正確地輸出中序遍歷序列。下圖是一個生成三序的例子。

2、實驗中的問題及一些說明:

(1)如果沒有虛空結點,能否通過三序得到唯一對應的二叉樹?

答案是肯定的。下面將簡單地說明如何利用先序序列和中序序列得到對應的二叉樹。

A)分析:

對於一個先序序列來說,第一個結點一定是根結點,它的右邊是左子樹的先序序列,然后是右子樹的先序序列。這里的問題在於,無法找到左、右子樹序列的分界點,換句話說,僅靠一個先序序列我們無法得到對應的二叉樹。

這時我們再對中序序列的構成分析,我們可以在中序序列中找到根結點(根結點的標號已經在先序序列中得到了),它的左邊是左子樹的中序序列,右邊是右子樹的中序序列。

這時候我們發現,這里出現了一個子結構。因為一棵樹(子樹)的三序序列長度是相等的,所以我們可以得到左、右子樹的先序序列,從而對於任一子樹來說,我們得到了它的先序序列和中序序列,問題轉換成了相似的子問題。

B)實現:

a)先序序列第一個結點找到根結點;

b)在中序序列中找到該結點(此處可以順序查找,找到結束即可;當數據量比較大時推薦使用二分法查找),得到左、右子樹的結點數,從而分別得到左、右子樹對應的先序序列和中序序列;

c)分別遞歸進入左、右子樹,建樹。遞歸的邊界是葉子結點,它的先序和中序都是長度為1的自己的標號。

(2)包含虛空結點的后序序列如何生成對應的二叉樹?

有一個簡單的辦法,從后往前掃描后序序列,相當於是順序為“根結點-右子樹-左子樹”生成的序列。這個與先序序列生成對應二叉樹是一個鏡面的過程,不再贅述。

(3)中序序列能夠生成唯一對應的二叉樹嗎?

答案是否定的。中序序列與二叉樹並不是1-1對應的,下圖是一個例子:

這兩個都是合法的二叉樹,兩者的中序序列都是CBA,即使是加上了虛空結點,得到的中序序列也都是$C$B$A$$表示空格,也就是虛空結點),所以中序序列與二叉樹不是1-1對應的,當然不能通過中序序列生成一棵唯一的二叉樹。

(2)能否不使用遞歸操作?

這個問題可以歸結為研究遞歸操作的本質。

遞歸操作實際上是一個對棧的操作,以先序遍歷二叉樹為例:將當前結點壓棧;如果左子樹非空,將左子樹壓棧;如果右子樹非空,將右子樹壓棧;如果左、右子樹都是空的,即當前結點為葉子結點,彈棧;當結點的左右子樹都完成了操作,彈棧。

於是我們得到了一個有意思的結果:所有的遞歸算法是可以有非遞歸調用的實現方式的(這里的非遞歸調用實現方式指的是遞歸調用函數,其本質應該還是遞歸)。當然,也不排除一些遞歸解決的問題同樣可以用遞推解決,比如漢諾塔問題,這里不贅述。

附:c++源代碼:

 

 1 /*
 2 項目:創建二叉樹,三序遍歷
 3 作者:張譯尹 
 4 */ 
 5 #include <iostream>
 6 #include <cstdio>
 7 
 8 using namespace std;
 9 
10 template <class T> class BiTree
11 {
12 private:
13     T data; //根結點的標志
14     BiTree *lch, *rch; //左右兒子
15 public:
16     void InitBiTree() //初始化二叉鏈表
17     {
18         data = 0;
19         lch = rch = NULL;
20     }
21     BiTree* CreatBiTree() //先序(帶虛空結點)遞歸建立二叉鏈表 
22     {
23         BiTree <char> *rt = new BiTree;
24         rt -> InitBiTree();
25         char ch;
26         scanf("%c", &ch);
27         if(ch != ' ') //非空指針,遞歸建樹
28         {
29             rt -> data = ch;
30             rt -> lch = CreatBiTree();
31             rt -> rch = CreatBiTree();
32         } 
33         else
34         {
35             delete rt;
36             rt = NULL;
37         }
38         return rt;
39     } 
40     void PreOrderTraverse() //遞歸先序遍歷 
41     {
42         printf("%c", data);
43         if(lch)
44             lch -> PreOrderTraverse();
45         if(rch)
46             rch -> PreOrderTraverse();
47     }
48     void InOrderTraverse() //遞歸中序遍歷
49     {
50         if(lch)
51             lch -> InOrderTraverse();
52         printf("%c", data);
53         if(rch)
54             rch -> InOrderTraverse();
55     } 
56     void PostOrderTraverse() //遞歸后序遍歷
57     {
58         if(lch)
59             lch -> PostOrderTraverse();
60         if(rch)
61             rch -> PostOrderTraverse();
62         printf("%c", data);
63     } 
64 };
65 
66 int main()
67 {
68     BiTree <char> *Tree;
69     //Tree -> InitBiTree();
70     
71     printf("請輸入二叉樹的先序列,結點用字母表示,空域用空格表示。\n");
72     Tree = Tree -> CreatBiTree();
73     
74     printf("先序序列:\n");
75     Tree -> PreOrderTraverse();
76     printf("\n");
77     
78     printf("中序序列:\n");
79     Tree -> InOrderTraverse();
80     printf("\n");
81     
82     printf("后序序列:\n");
83     Tree -> PostOrderTraverse();
84     printf("\n");
85     
86     return 0;
87 }
View Code

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM