心中有“树”:数据结构之树详解
文章目錄
- 前言
- (一)樹的基礎(chǔ)定義與表示
- 1 樹的定義
- 2 樹的圖示
- 3 樹的邏輯結(jié)構(gòu)表示法
- (二)二叉樹
- 1 二叉樹定義
- 2 二叉樹示意圖
- 3 程序?qū)崿F(xiàn)
- (1)節(jié)點(diǎn)定義
- (2)二叉樹的先序遍歷
- (3)二叉樹非遞歸先序遍歷
- (4)二叉樹層序遍歷
- (5)計(jì)算二叉樹的深度
- 4 二叉樹算法設(shè)計(jì)題目
- 1 查找元素為X的節(jié)點(diǎn)
- 2 判斷二叉樹是否為完全二叉樹
- (三)樹
- 1 樹的存儲(chǔ)結(jié)構(gòu)
- 2 存儲(chǔ)結(jié)構(gòu)圖示
- 3 樹的先序遍歷
- 4 求樹的深度
- (四)總結(jié)
- (五)參考文獻(xiàn)
前言
樹型結(jié)構(gòu)是一類重要的非線性數(shù)據(jù)結(jié)構(gòu)。其中以樹和二叉樹最為常用。樹狀結(jié)構(gòu)在客觀世界中廣泛存在,如人類社會(huì)的族譜和社會(huì)組織機(jī)構(gòu)等,都可以用樹來(lái)形象表示。同時(shí)在計(jì)算機(jī)領(lǐng)域中,樹可以用來(lái)表示源程序的語(yǔ)法結(jié)構(gòu),并用于加密信息。本文主要討論二叉樹極其各種操作與應(yīng)用,并拓展樹的結(jié)構(gòu)與簡(jiǎn)單操作。
(一)樹的基礎(chǔ)定義與表示
1 樹的定義
樹(Tree)是n(n>=0)個(gè)節(jié)點(diǎn)的有限集。在任意一顆非空樹中:
(1)有且僅有一個(gè)特定的根(Root)的節(jié)點(diǎn);
(2)n>1時(shí)其余節(jié)點(diǎn)可分為m(m>0)個(gè)互不相交的有限集合T1,T2,…Tm,其中每一個(gè)集合本身又是一顆樹,并且稱為根的子樹
顯然,這是一個(gè)遞歸的定義。故對(duì)于樹的操作等也需要使用遞歸操作。
2 樹的圖示
設(shè)有樹a,b,c,如上圖所示,其中a為空樹,b只有一個(gè)根節(jié)點(diǎn)為A,c的根節(jié)點(diǎn)為A,它又有三顆子樹,假設(shè)為T1,T2,T3,這三顆子樹又分別有自己的根B,C,D和相應(yīng)的更小的子樹,依此類推。
3 樹的邏輯結(jié)構(gòu)表示法
(1)層次表示法
前面的樹的圖示所用的表示法即層次表示法,它可以直觀表示樹中個(gè)節(jié)點(diǎn)的層次關(guān)系。同時(shí)可以更好地理解關(guān)于樹的各種操作
(2)嵌套表示法
嵌套表示法采用集合形式表示邏輯結(jié)構(gòu),類似于集合中的韋恩圖,應(yīng)用較少,此不過(guò)多介紹。
(3)廣義表表示法
廣義表利用根節(jié)點(diǎn)元素和括號(hào)的組合表示。括號(hào)外邊是根節(jié)點(diǎn),括號(hào)里邊是每個(gè)子樹。如(2)中的圖示中 c 樹可以表示為 A(B(E(K,L),F),C(G),D(H(M),I,J)))。
(二)二叉樹
1 二叉樹定義
二叉樹是最常用的一種樹型結(jié)構(gòu),其特點(diǎn)是每個(gè)節(jié)點(diǎn)最多只有兩顆子樹(即二叉樹中不存在度大于2的節(jié)點(diǎn)),二叉樹的子樹有左右之分,次序不能任意顛倒。
2 二叉樹示意圖
二叉樹的順序表表示法較為繁瑣,且浪費(fèi)空間,我們以二叉鏈表表示法為例:
將左圖層次表示與節(jié)點(diǎn)的存儲(chǔ)結(jié)構(gòu)關(guān)聯(lián)即右圖所示。每個(gè)節(jié)點(diǎn)有一個(gè)數(shù)據(jù)域,與兩個(gè)指針域,分別指向左孩子與右孩子。同時(shí)每個(gè)左孩子又是一顆子樹。在這里我們以 ^ 符號(hào)表示指針域?yàn)榭?#xff08;NULL)表示其沒有左(右)子樹。
3 程序?qū)崿F(xiàn)
前面說(shuō)了二叉樹的定義,接下來(lái)看一下關(guān)鍵的程序代碼。
(1)節(jié)點(diǎn)定義
二叉樹的二叉鏈表存儲(chǔ)結(jié)構(gòu)中,節(jié)點(diǎn)擁有一個(gè)數(shù)據(jù)域存儲(chǔ)元素,此處元素類型我們以字符型為例,兩個(gè)指針域存儲(chǔ)左右孩子節(jié)點(diǎn)的地址。
二叉鏈表表示法
typedef struct BiTNode {char data; //數(shù)據(jù)元素BiTNode* lc, * rc;//分別指向左孩子 與 右}*BiTree;lc,rc分別為指向左右孩子節(jié)點(diǎn)的地址的指針。BiTNode為節(jié)點(diǎn)結(jié)構(gòu)體類型,為了代碼編寫方便,同時(shí)我們將指向BiTNode的指針的類型定義為BiTree。BiTree相當(dāng)于BiTNode*。同時(shí)二叉樹還有三叉鏈表表示法,其節(jié)點(diǎn)定義多了一個(gè)指向雙親的指針域。即BiTNode* parent,這樣可以更方便的尋找某個(gè)節(jié)點(diǎn)的雙親節(jié)點(diǎn)。
(2)二叉樹的先序遍歷
二叉樹的遍歷分為先序遍歷,中序遍歷,后序遍歷和層序遍歷。
先序遍歷:
(1)訪問(wèn)根節(jié)點(diǎn)——D;
(2)遍歷左子樹——L;
(3)遍歷右子樹——R;
先序遍歷簡(jiǎn)稱為DLR遍歷,同理中序?yàn)長(zhǎng)DR遍歷,后序?yàn)長(zhǎng)RD遍歷。下面我們以先序遍歷程序?yàn)槔?#xff0c;中序和后序僅需要調(diào)整程序順序即可。
其中visit為函數(shù)作為形參,為了使得代碼具有通用性。具體visit實(shí)現(xiàn)可以是最簡(jiǎn)單的輸出,或者對(duì)于節(jié)點(diǎn)進(jìn)行的一系列操作。函數(shù)作為形參參考下列代碼。
#include<iostream> int num[] = { 1,2,3,4 }; void Print(int m) { std::cout << m << std::endl; } void ArrayPrint(int num[], void visit(int)) {for (int i = 0; i < 4; i++)visit(num[i]); } int main() {ArrayPrint(num, Print); }(3)二叉樹非遞歸先序遍歷
由于遞歸實(shí)際是棧的壓棧出棧操作,所以我們可以將遞歸的先序遍歷改為非遞歸的棧操作。
算法思路:從根節(jié)點(diǎn)開始,每訪問(wèn)一個(gè)節(jié)點(diǎn),按照前序遍歷規(guī)則走左子樹,如果系欸點(diǎn)的右子樹存在,那么將右指針入棧,以便正確遍歷相應(yīng)子樹。非遞歸操作不常用,對(duì)二叉樹的操作基本采用遞歸形式容易理解。
(4)二叉樹層序遍歷
二叉樹層序遍歷需要用到隊(duì)列。即訪問(wèn)根節(jié)點(diǎn)的同時(shí),將其左右孩子一次入隊(duì)列,較為簡(jiǎn)單。利用STL封裝的queue實(shí)現(xiàn)。代碼程序如下:
void QueuePreorder(BiTree T) {queue <BiTree> q;if(!T)q.push(T);while (!q.empty()) //隊(duì)列不為空判斷{visit(q.front()->data);if(!q.front()->lc)q.push(q.front()->lc);if(!q.front()->rc)q.push(q.front()->rc);q.pop(); //已遍歷節(jié)點(diǎn)出隊(duì)} }(5)計(jì)算二叉樹的深度
//求二叉樹的深度 int depth(BiTree T) {if (!T)return 0;int dl, dr;//分別表示左子樹深度與右子樹深度dl = depth(T->lc); dr = depth(T->rc);return dl > dr ? dl + 1 : dr + 1;//加一為 加上根節(jié)點(diǎn)所在深度 }4 二叉樹算法設(shè)計(jì)題目
1 查找元素為X的節(jié)點(diǎn)
題目:再二叉樹中查找是否有元素為X的節(jié)點(diǎn),找到則返回節(jié)點(diǎn)地址,否則返回空指針
分析:題目比較簡(jiǎn)單。可以直接在二叉樹中遞歸查找,如果根是空,返回NULL。如果元素為X,返回地址。否則新建節(jié)點(diǎn)M接收左子樹返回的值,如果非空返回M。否則返回右子樹返回的值。同時(shí)遞歸時(shí),我們只需要考慮當(dāng)前一層的情況,具體內(nèi)部遞歸回代實(shí)現(xiàn)可以不必詳細(xì)了解,否則很容易被繞進(jìn)去。程序?qū)崿F(xiàn)為
在這里我們需要用M來(lái)保存遞歸左子樹返回的值,否則若只有兩個(gè)TreeSearch的順序結(jié)構(gòu),則只有第二個(gè)TreeSearch起作用,左子樹是否右X不得而知。局部錯(cuò)誤代碼為:
else {//錯(cuò)誤TreeSearch(T->lc,e);TreeSearch(T->rc,e); }2 判斷二叉樹是否為完全二叉樹
完全二叉樹:深度為k的,有n個(gè)節(jié)點(diǎn)的二叉樹,當(dāng)且僅當(dāng)其每一個(gè)節(jié)點(diǎn)都與深度為k的滿二叉樹的1至n的節(jié)點(diǎn)一一對(duì)應(yīng)時(shí),稱為完全二叉樹。
簡(jiǎn)單來(lái)說(shuō),就是只可能最后一層節(jié)點(diǎn)是不滿的,同時(shí)節(jié)點(diǎn)應(yīng)盡可能向左補(bǔ)充。
算法思路:可以利用層序遍歷,首先將根節(jié)點(diǎn)入隊(duì)。當(dāng)可以從隊(duì)列取出元素且不空,則將左右孩子入隊(duì)。當(dāng)出現(xiàn)空指針結(jié)束循環(huán)。最后再用一個(gè)while循環(huán)如果遍歷到了非空元素則為非完全二叉樹,否則最終為完全二叉樹。(因?yàn)橥耆鏄渲挥凶詈笠慌趴梢钥展?jié)點(diǎn),也就是遇到空指針后,后面的層序遍歷只能都是空指針)。代碼實(shí)現(xiàn)為:
(三)樹
1 樹的存儲(chǔ)結(jié)構(gòu)
此處推廣至更為普遍的樹結(jié)構(gòu),一個(gè)節(jié)點(diǎn)可以擁有多個(gè)孩子節(jié)點(diǎn)。常見表示法為雙親表示法、孩子表示法、孩子-兄弟表示法(二叉樹表示法),此處以更為常見的孩子兄弟表示法為例。
typedef struct CSNode {int data;CSNode* firstchild,*nextsibling;// }*Tree;2 存儲(chǔ)結(jié)構(gòu)圖示
孩子-兄弟表示法又稱為二叉樹表示法或二叉鏈表表示法。鏈表中節(jié)點(diǎn)的兩個(gè)指針域分別指向該節(jié)點(diǎn)的第一個(gè)孩子節(jié)點(diǎn)和下一個(gè)兄弟節(jié)點(diǎn)(即與第一個(gè)孩子節(jié)點(diǎn)在同一層)。
3 樹的先序遍歷
如果根節(jié)點(diǎn)為空,返回;否則訪問(wèn)根節(jié)點(diǎn)。然后依次遍歷第一個(gè)孩子節(jié)點(diǎn)和它的兄弟節(jié)點(diǎn)(以其為根的子樹),此處可以用一個(gè)for循環(huán)。下面以fc代表第一個(gè)孩子節(jié)點(diǎn)指針域,ns代表兄弟節(jié)點(diǎn)指針域。程序?qū)崿F(xiàn)為:
void TreePreorder(Tree T,void visit(TElemType)) {Tree p;if(!T)return;visit(T->data);for(p=T->fc;p;p=p->ns)TreePreorder(p,visit); }讀者可依據(jù)二叉樹的某些操作實(shí)現(xiàn)樹的一些操作,算法思路沒有太大變化。
4 求樹的深度
樹的深度為各個(gè)子樹的最大深度加一,可一次遍歷遞歸所有子樹,最后返回深度加一。程序?qū)崿F(xiàn):
int TreeDepth(Tree T) {int m1;Tree p;int m2;//記錄最大深度if(!T)return 0;for(m2=0,p=T->fc;p;p=p->ns)//循環(huán)求出子樹深度最大值,最后加上根節(jié)點(diǎn)層{m1=TreeDepth(p);//以p為根節(jié)點(diǎn)的子樹深度if(m1>m2)m2=m1;}return m2+1; }(四)總結(jié)
1 本篇文章總結(jié)了以二叉樹為主的樹的一些常用算法,包括對(duì)其的遍歷與一些算法設(shè)計(jì)題目。大部分通過(guò)遞歸實(shí)現(xiàn),可在理解遞歸基礎(chǔ)上編寫相應(yīng)算法。
2 有關(guān)于樹的數(shù)據(jù)結(jié)構(gòu)還有很多,例如Huffman樹,二叉排序樹等主要數(shù)據(jù)結(jié)構(gòu)和算法,讀者可以自行學(xué)習(xí),此處沒有詳細(xì)介紹。
3 本章討論的樹是數(shù)據(jù)結(jié)構(gòu)中非線性數(shù)據(jù)結(jié)構(gòu)中很重要的一類,是一種層次模型的數(shù)據(jù)結(jié)構(gòu)。它可以較好地描述數(shù)據(jù)間地層次關(guān)系,能對(duì)數(shù)據(jù)機(jī)構(gòu)隨機(jī)再組織,故樹一般成為動(dòng)態(tài)數(shù)據(jù)結(jié)構(gòu)。
(五)參考文獻(xiàn)
【1】嚴(yán)蔚敏.吳偉民. 數(shù)據(jù)結(jié)構(gòu)(C語(yǔ)言版). 北京:清華大學(xué)出版社,1997.
【2】齊悅.夏克儉.姚琳. 數(shù)據(jù)結(jié)構(gòu)、算法與應(yīng)用. 北京:清華大學(xué)出版社.2015.
【3】聶立新.桑兆陽(yáng).張華清. 數(shù)據(jù)結(jié)構(gòu)與算法. 中國(guó)石油大學(xué)出版社.2017.
總結(jié)
以上是生活随笔為你收集整理的心中有“树”:数据结构之树详解的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: RocketMQ:消息存储机制详解与源码
- 下一篇: 【洛谷】马的遍历--广度优先搜索(BFS