二叉樹(shù)常見(jiàn)操作的 C++ 實(shí)現(xiàn)(一)

本節(jié)主要講述二叉樹(shù)中常見(jiàn)操作的 C++ 實(shí)現(xiàn),重點(diǎn)是代碼的實(shí)現(xiàn)净响,不拘泥于一種方法少欺。
后續(xù)會(huì)根據(jù)做的一些題增加樹(shù)的其他操作,并在個(gè)人的 github 持續(xù)更新中馋贤。



本部分基本涵蓋了二叉樹(shù)的基本操作赞别,后續(xù)新增內(nèi)容會(huì)在個(gè)人的 github 上持續(xù)更新。
此處對(duì)于二叉樹(shù)的基本概念就不做贅述了配乓,聚焦于代碼上的實(shí)現(xiàn)仿滔。

二叉樹(shù)的創(chuàng)建

首先,c++ 文件的基本結(jié)構(gòu)走起來(lái)犹芹。

#include <bits/stdc++.h>

using namespace std;

int main(){
    
    return 0;
}

之后崎页,考慮二叉樹(shù)的節(jié)點(diǎn),為了泛用性腰埂,可以使用 typedef飒焦。

typedef string ElemType;

這里我們使用 string 類(lèi)型作為節(jié)點(diǎn)類(lèi)型,當(dāng)然你也可以自定義其他類(lèi)型。

然后定義樹(shù)的節(jié)點(diǎn)牺荠,顯然翁巍,根據(jù)樹(shù)的定義,我們需要一個(gè)數(shù)據(jù)域 data 和兩個(gè)節(jié)點(diǎn)類(lèi)型的指針域 left 和 right休雌,分別指向其左子樹(shù)和右子樹(shù)灶壶,使用 struct 類(lèi)型即可。
完成這些后杈曲,我們?cè)诮Y(jié)構(gòu)體里創(chuàng)建一個(gè)給節(jié)點(diǎn)數(shù)據(jù)域賦值的構(gòu)造函數(shù)驰凛,其指針域都默認(rèn)為空。
結(jié)構(gòu)如下:

typedef string ElemType;

//定義樹(shù)的節(jié)點(diǎn)
typedef struct BinaryNode{
    //節(jié)點(diǎn)
    ElemType data;
    //左子樹(shù)
    BinaryNode* left;
    //右子樹(shù)
    BinaryNode* right;
    BinaryNode(ElemType value){
        data = value;
        left = NULL;
        right = NULL;
    }
}BinaryNode,*BiTree;

接下來(lái)担扑,我們定義一個(gè)二叉樹(shù)類(lèi)恰响,其私有成員部分有樹(shù)根節(jié)點(diǎn) mRoot ,節(jié)點(diǎn)總數(shù)(可有可無(wú)) size 魁亦,相關(guān)成員函數(shù)渔隶,共有成員部分也有相關(guān)成員函數(shù)羔挡。
二叉樹(shù)的類(lèi)如下:

//二叉樹(shù)
class BinaryTree{
private:
    //根節(jié)點(diǎn)
    BiTree mRoot;
    //節(jié)點(diǎn)總數(shù)
    int size;
    //按前序遍歷方式遞歸創(chuàng)建二叉樹(shù)
    BiTree create();
    //遞歸實(shí)現(xiàn)前序遍歷
    void preOrder(BiTree root);
    //非遞歸實(shí)現(xiàn)前序遍歷
    void nonRec_preOrder(BiTree root);
    //遞歸實(shí)現(xiàn)中序遍歷
    void inOrder(BiTree root);
    //非遞歸實(shí)現(xiàn)中序遍歷
    void nonRec_inOrder(BiTree root);
    //遞歸實(shí)現(xiàn)后序遍歷
    void postOrder(BiTree root);
    //非遞歸實(shí)現(xiàn)后序遍歷
    void nonRec_postOrder(BiTree root);
    //非遞歸實(shí)現(xiàn)層次遍歷
    void nonRec_levelOrder(BiTree root);
    //遞歸實(shí)現(xiàn)摧毀樹(shù)(前序遍歷)
    void destroy(BiTree root);
    //遞歸得到樹(shù)的節(jié)點(diǎn)數(shù)
    int getSize(BiTree root);
    //遞歸得到樹(shù)的高度
    int getHeight(BiTree root);
    //得到葉子節(jié)點(diǎn)的個(gè)數(shù)
    int getLeafs(BiTree root);
    //得到度為1的節(jié)點(diǎn)個(gè)數(shù)
    int getOneLeafs(BiTree root);
    //得到滿節(jié)點(diǎn)個(gè)數(shù)
    int getFullLeafs(BiTree root);
    //獲取第 k 層的節(jié)點(diǎn)數(shù)
    int getLevelSize(BiTree root,int level);
    //查找指定值的節(jié)點(diǎn)
    BiTree findNode(BiTree root,ElemType value);

public:
    //按前序遍歷方式遞歸創(chuàng)建二叉樹(shù)
    void createTree();
    //遞歸實(shí)現(xiàn)前序遍歷
    void preOrder();
    //非遞歸實(shí)現(xiàn)前序遍歷
    void nonRec_preOrder();
    //遞歸實(shí)現(xiàn)中序遍歷
    void inOrder();
    //非遞歸實(shí)現(xiàn)中序遍歷
    void nonRec_inOrder();
    //遞歸實(shí)現(xiàn)后序遍歷
    void postOrder();
    //非遞歸實(shí)現(xiàn)后序遍歷
    void nonRec_postOrder();
    //遞歸實(shí)現(xiàn)層次遍歷
    void nonRec_levelOrder();
    //遞歸實(shí)現(xiàn)摧毀樹(shù)(前序遍歷)
    void destroy();
    //遞歸得到樹(shù)的節(jié)點(diǎn)數(shù)
    int getSize();
    //遞歸得到樹(shù)的高度
    int getHeight();
    //得到葉子節(jié)點(diǎn)的個(gè)數(shù)
    int getLeafs();
    //得到度為1的節(jié)點(diǎn)個(gè)數(shù)
    int getOneLeafs();
    //得到滿節(jié)點(diǎn)個(gè)數(shù)
    int getFullLeafs();
    //獲取第 k 層的節(jié)點(diǎn)數(shù)
    int getLevelSize(int level);
    //判斷是否為完全二叉樹(shù)
    bool isCompleteTree();
    //查找指定值的節(jié)點(diǎn)
    BiTree findNode(ElemType value);

};

后續(xù)繼續(xù)增加一些功能洁奈,本系列的任務(wù)先實(shí)現(xiàn)上面給出所有的操作。

我們先來(lái)看按前序遍歷方式遞歸創(chuàng)建二叉樹(shù)绞灼,
私有函數(shù)部分:

void preOrder(BiTree root);

共有函數(shù)部分:

//按前序遍歷方式遞歸創(chuàng)建二叉樹(shù)
void createTree();

之后的操作基本上都分為相應(yīng)的私有函數(shù)部分和共有函數(shù)部分利术,
同時(shí)一個(gè)功能的實(shí)現(xiàn)先給出私有函數(shù)部分,再給出其共有函數(shù)部分低矮。

來(lái)看創(chuàng)建印叁,我們使用的是前序遍歷的方式,空節(jié)點(diǎn)使用 "#" (因?yàn)檩斎氲氖亲址?lèi)型)军掂。
首先呢轮蜕,我們創(chuàng)建型節(jié)點(diǎn) newNode ,若輸入的值為 "#" 蝗锥,則返回為空跃洛,因?yàn)槭强展?jié)點(diǎn)。
然后將輸入的值 value 賦予新節(jié)點(diǎn)的數(shù)據(jù)域终议,最后遞歸創(chuàng)建該節(jié)點(diǎn) newNode 的左子樹(shù)和右子樹(shù)汇竭。
代碼如下:

//按前序遍歷方式遞歸創(chuàng)建二叉樹(shù)
BiTree BinaryTree::create(){
    BiTree newNode = NULL;
    ElemType value;
    //此處 ElemType 應(yīng)該是基本類(lèi)型數(shù)據(jù),若為自定義類(lèi)型,請(qǐng)重載輸入流
    cin>>value;
    //# 表示當(dāng)前子樹(shù)為空
    if(value == "#"){
        return NULL;
    }else{
        //遞歸創(chuàng)建左右子樹(shù),使用 size 記錄下樹(shù)的節(jié)點(diǎn)樹(shù)
        size++;
        newNode = new BinaryNode(value);
        newNode->left = create();
        newNode->right = create();
        return newNode;
    }
}
void BinaryTree::createTree(){
    size = 0;
    mRoot = create();
}

這樣一棵樹(shù)就已經(jīng)創(chuàng)建好了,當(dāng)然樹(shù)的創(chuàng)建方式有很多穴张,這里只列舉了一種细燎,之后還會(huì)增加其他方式。
來(lái)測(cè)試下吧:

int main(){
    BinaryTree tree;
    cout<<"Please enter the tree in the previous order traversal mode.If the node is empty,use # instead:"<<endl;
    //"A B D G # # H # # # C E # I # # F # #";
    tree.createTree();
    cout<<&tree<<endl;
    return 0;
}

打印的結(jié)果為:

Please enter the tree in the previous order traversal mode.If the node is empty,use # instead:
A B D G # # H # # # C E # I # # F # #
0x62fe10

&tree 的內(nèi)容可能有所不同皂甘,有結(jié)果玻驻,說(shuō)明這棵樹(shù)已經(jīng)創(chuàng)建成功了!

樹(shù)的遍歷(遞歸 + 非遞歸)

在創(chuàng)建一棵樹(shù)后偿枕,我們總想看看樹(shù)里面有什么內(nèi)容璧瞬,那就遍歷嘛佛析。
樹(shù)的遍歷常見(jiàn)的有四種,前序遍歷彪蓬,中序遍歷寸莫,后序遍歷,層次遍歷档冬。
前三種既可以使用遞歸方法膘茎,也可以使用非遞歸。
層序遍歷就是非遞歸了酷誓。
非遞歸過(guò)程中會(huì)用到 棧與隊(duì)列 披坏,DFS 和 BFS 等概念。

前序遍歷(遞歸 + 非遞歸)

先來(lái)看前序遍歷的盐数,前序遍歷的順序是根節(jié)點(diǎn)棒拂,左子樹(shù),右子樹(shù)玫氢。
遞歸方法很簡(jiǎn)單帚屉,代碼如下:

//遞歸實(shí)現(xiàn)前序遍歷
void BinaryTree::preOrder(BiTree root){
    if(root != NULL){
        cout<<root->data<<" ";
        preOrder(root->left);
        preOrder(root->right);
    }
}
void BinaryTree::preOrder(){
    cout<<"The result of the preorder traversal is "<<endl;
    preOrder(mRoot);
    cout<<endl;
}

非遞歸需要用到 的思想,和非遞歸遍歷思想類(lèi)似漾峡。我們還需要用棧保存節(jié)點(diǎn)攻旦,并訪問(wèn)數(shù)據(jù)域。
具體的代碼如下:

//非遞歸實(shí)現(xiàn)前序遍歷
void BinaryTree::nonRec_preOrder(BiTree root){
    if(root == NULL){
        return ;
    }
    stack<BiTree> s;
    BiTree p = root;
    s.push(p);
    while(!s.empty()){
        BiTree node = s.top();
        cout<<node->data<<" ";
        s.pop();
        if(node->right){
            s.push(node->right);
        }
        if(node->left){
            s.push(node->left);
        }
    }
}
void BinaryTree::nonRec_preOrder(){
    cout<<"The result of the non-recursive preorder traversal is "<<endl;
    nonRec_preOrder(mRoot);
    cout<<endl;
}

測(cè)試部分等四種遍歷講完后統(tǒng)一給出生逸。

中序遍歷(遞歸 + 非遞歸)

先來(lái)看中序遍歷的牢屋,中序遍歷的順序是左子樹(shù),根節(jié)點(diǎn)槽袄,右子樹(shù)烙无。
遞歸方法很簡(jiǎn)單,代碼如下:

//遞歸實(shí)現(xiàn)中序遍歷
void BinaryTree::inOrder(BiTree root){
    if(root != NULL){
        inOrder(root->left);
        cout<<root->data<<" ";
        inOrder(root->right);
    }
}
void BinaryTree::inOrder(){
    cout<<"The result of the in-order traversal is "<<endl;
    inOrder(mRoot);
    cout<<endl;
}

在之前的非遞歸前序遍歷的基礎(chǔ)上遍尺,中序遍歷也就不難了截酷。
具體代碼如下:

//非遞歸實(shí)現(xiàn)中序遍歷
void BinaryTree::nonRec_inOrder(BiTree root){
    if(root == NULL){
        return ;
    }
    stack<BiTree> myStack;
    BiTree rt = root;
    while(rt != NULL || !myStack.empty()){
        while(rt != NULL){
            myStack.push(rt);
            rt = rt->left;
        }
        rt = myStack.top();
        myStack.pop();
        cout<<rt->data<<" ";
        rt = rt->right;
    }
}
void BinaryTree::nonRec_inOrder(){
    cout<<"The result of the non-recursive in-order traversal is "<<endl;
    nonRec_inOrder(mRoot);
    cout<<endl;
}
后序遍歷(遞歸 + 非遞歸)

先來(lái)看后序遍歷的,后序遍歷的順序是左子樹(shù)狮鸭,右子樹(shù)合搅,根節(jié)點(diǎn)。
遞歸方法很簡(jiǎn)單歧蕉,代碼如下:

//遞歸實(shí)現(xiàn)后序遍歷
void BinaryTree::postOrder(BiTree root){
    if(root != NULL){
        postOrder(root->left);
        postOrder(root->right);
        cout<<root->data<<" ";
    }
}
void BinaryTree::postOrder(){
    cout<<"The result of the postorder traversal is "<<endl;
    postOrder(mRoot);
    cout<<endl;
}

仿照之前的非遞歸遍歷灾部,后序遍歷的非遞歸的具體代碼如下:

//非遞歸實(shí)現(xiàn)后序遍歷,雙棧法
/*
    棧 s1 入棧順序:根節(jié)點(diǎn)-> 左節(jié)點(diǎn)-> 右節(jié)點(diǎn)
    棧 s2 入棧順序:右節(jié)點(diǎn)-> 左節(jié)點(diǎn)-> 根節(jié)點(diǎn)
    出棧順序:根節(jié)點(diǎn)-> 左節(jié)點(diǎn)-> 右節(jié)點(diǎn)
*/
void BinaryTree::nonRec_postOrder(BiTree root){
    if(root == NULL){
        return ;
    }
    stack<BiTree> s1;
    stack<BiTree> s2;
    s1.push(root);
    while(!s1.empty()){
        BiTree p = s1.top();
        s1.pop();
        s2.push(p);
        if(p->left){
            s1.push(p->left);
        }
        if(p->right){
            s1.push(p->right);
        }
    }
    while(!s2.empty()){
        cout<<s2.top()->data<<" ";
        s2.pop();
    }
}
void BinaryTree::nonRec_postOrder(){
    cout<<"The result of the non-recursive postorder traversal is "<<endl;
    nonRec_postOrder(mRoot);
    cout<<endl;
}
層次遍歷

層次遍歷需要用到隊(duì)列保存每一層的節(jié)點(diǎn),一層層地訪問(wèn)惯退,它不需要用到遞歸赌髓。
具體代碼如下:

//非遞歸實(shí)現(xiàn)層次遍歷
void BinaryTree::nonRec_levelOrder(BiTree root){
    queue<BiTree> q;
    q.push(root);
    while(!q.empty()){
        //每層的節(jié)點(diǎn)
        int num_level = q.size();
        while(num_level--){
            BiTree node = q.front();
            q.pop();
            cout<<node->data<<" ";
            if(node->left){
                q.push(node->left);
            }
            if(node->right){
                q.push(node->right);
            }
        }
    }
}
void BinaryTree::nonRec_levelOrder(){
    cout<<"The result of the non-recursive sequence traversal is "<<endl;
    nonRec_levelOrder(mRoot);
    cout<<endl;
}

匯總

我們來(lái)創(chuàng)建一棵樹(shù),并使用上面的方法分別遍歷這棵樹(shù)。
測(cè)試代碼如下:

int main(){
    BinaryTree tree;
    cout<<"Please enter the tree in the previous order traversal mode.If the node is empty,use # instead:"<<endl;
    //"A B D G # # H # # # C E # I # # F # #";
    tree.createTree();
    cout<<&tree<<endl;
    //前序遍歷
    tree.preOrder();
    //非遞歸前序遍歷
    tree.nonRec_preOrder();
    //中序遍歷
    tree.inOrder();
    //非遞歸中序遍歷
    tree.nonRec_inOrder();
    //后序遍歷
    tree.postOrder();
    //非遞歸后序遍歷
    tree.nonRec_postOrder();
    //非遞歸層次遍歷
    tree.nonRec_levelOrder();
    return 0;
}

打印的結(jié)果如下:

Please enter the tree in the previous order traversal mode.If the node is empty,use # instead:
A B D G # # H # # # C E # I # # F # #
0x62fe10
The result of the preorder traversal is
A B D G H C E I F
The result of the non-recursive preorder traversal is
A B D G H C E I F
The result of the in-order traversal is
G D H B A E I C F
The result of the non-recursive in-order traversal is
G D H B A E I C F
The result of the postorder traversal is
G H D B I E F C A
The result of the non-recursive postorder traversal is
G H D B I E F C A
The result of the non-recursive sequence traversal is
A B C D E F G H I

結(jié)果和我們畫(huà)的樹(shù)是一致的锁蠕。

本節(jié)的內(nèi)容就到這里了夷野,之后繼續(xù)實(shí)現(xiàn)其他的功能。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末荣倾,一起剝皮案震驚了整個(gè)濱河市悯搔,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌舌仍,老刑警劉巖妒貌,帶你破解...
    沈念sama閱讀 218,284評(píng)論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異铸豁,居然都是意外死亡灌曙,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,115評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門(mén)节芥,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)在刺,“玉大人,你說(shuō)我怎么就攤上這事头镊◎纪眨” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 164,614評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵拧晕,是天一觀的道長(zhǎng)隙姿。 經(jīng)常有香客問(wèn)我,道長(zhǎng)厂捞,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,671評(píng)論 1 293
  • 正文 為了忘掉前任队丝,我火速辦了婚禮靡馁,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘机久。我一直安慰自己臭墨,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,699評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布膘盖。 她就那樣靜靜地躺著胧弛,像睡著了一般。 火紅的嫁衣襯著肌膚如雪侠畔。 梳的紋絲不亂的頭發(fā)上结缚,一...
    開(kāi)封第一講書(shū)人閱讀 51,562評(píng)論 1 305
  • 那天,我揣著相機(jī)與錄音软棺,去河邊找鬼红竭。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的茵宪。 我是一名探鬼主播最冰,決...
    沈念sama閱讀 40,309評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼稀火!你這毒婦竟也來(lái)了暖哨?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 39,223評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤凰狞,失蹤者是張志新(化名)和其女友劉穎鹿蜀,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體服球,經(jīng)...
    沈念sama閱讀 45,668評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡茴恰,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,859評(píng)論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了斩熊。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片往枣。...
    茶點(diǎn)故事閱讀 39,981評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖粉渠,靈堂內(nèi)的尸體忽然破棺而出分冈,到底是詐尸還是另有隱情,我是刑警寧澤霸株,帶...
    沈念sama閱讀 35,705評(píng)論 5 347
  • 正文 年R本政府宣布雕沉,位于F島的核電站,受9級(jí)特大地震影響去件,放射性物質(zhì)發(fā)生泄漏坡椒。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,310評(píng)論 3 330
  • 文/蒙蒙 一尤溜、第九天 我趴在偏房一處隱蔽的房頂上張望倔叼。 院中可真熱鬧,春花似錦宫莱、人聲如沸丈攒。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,904評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)巡验。三九已至,卻和暖如春碘耳,著一層夾襖步出監(jiān)牢的瞬間显设,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,023評(píng)論 1 270
  • 我被黑心中介騙來(lái)泰國(guó)打工藏畅, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留敷硅,地道東北人功咒。 一個(gè)月前我還...
    沈念sama閱讀 48,146評(píng)論 3 370
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像绞蹦,于是被迫代替她去往敵國(guó)和親力奋。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,933評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容