1. 程式人生 > >【資料結構】構造二叉樹的三種方法

【資料結構】構造二叉樹的三種方法

題目:

binary tree is a tree data structure in which each node has at most two children, which are referred to as the left child and the right child.

1、Three Constructor:
creat a tree by copy.
creat a tree by a array.(using BFS)
creat a tree by preorder and inorder.

2、Methods:
clear: clear the tree.
copyTree: input old tree root and new tree root, copy it and get a new tree.

3、Static Methods:
three way to search tree.

Hint:

‘#’是表示該節點不存在,在下層判斷的時候,可忽略該不存在的節點,主要在根據陣列構造樹中。

1 2 3 # 4 5 6 7 # # 8

得到的是

                1

          /            \

      2                  3

         \              /  \

           4         5      6

         /             \

        7              8

即把二叉樹作為一個方法封裝起來。
附上標頭檔案:

//
//  BinaryTree.hpp
//  C++
//
//  Created by my TA on 16/5/4.
//  Copyright © 2016年 lee. All rights reserved.
//

#ifndef BinaryTree_hpp
#define BinaryTree_hpp

#include <iostream>
#include <vector>
#include <queue>
#include <stack>

#define EMPTY_ELE '#'

class
BinaryTree { public: struct Node { char val; Node* left; Node* right; Node(char v, Node* l = NULL, Node* r = NULL): val(v), left(l), right(r) { } }; BinaryTree(const BinaryTree&); BinaryTree(std::vector<char>&); // created by preorder and inorder BinaryTree(const std::vector<char>& preOrder, const std::vector<char>& inOrder); ~BinaryTree(); Node* getRoot() const; void clear(); static void preOrder(void (*visite)(BinaryTree::Node*), Node*); static void inOrder(void (*visite)(BinaryTree::Node*), Node*); static void postOrder(void (*visite)(BinaryTree::Node*), Node*); private: Node * copyNode(Node * oldNode, Node * newNode); Node * root; }; #endif /* BinaryTree_hpp */

學到了不少東西。
先說說方法類的:
(一)、先序、中序、後序遍歷二叉樹,這是二叉樹最基本的操作方法。
具體的實現方法是遞迴函式,通過visit的位置來控制先訪問父節點,還是左子節點,還是右子節點。
具體實現程式碼如下:(其中visite的作用看具體的程式碼,這裡只是一個抽象函式)

void preOrder(Node* _root) {
    if (_root) {
        visite(_root);
        preOrder(_root->left);
        preOrder(_root->right);
    }
}
void inOrder(Node* _root) {
    if (_root) {
        inOrder(_root->left);
        visite(_root);
        inOrder(_root->right);
    }
}
void postOrder(Node* _root) {
    if (_root) {
        postOrder(_root->left);
        postOrder(_root->right);
        visite(_root);
    }
}

一個值得留心的點就是,搜尋這個節點之前,要先判斷它是否為空,可以用if(root)

(二)構造二叉樹的三種方法

(1)、creat a tree by copy.
BinaryTree(const BinaryTree& other_root)
給你一棵樹的根,讓你跟著拷貝一棵樹出來,我用的方法是遞迴,思路跟二叉樹的先序遍歷一樣。(把visit換成new Node就行)。
PS.這裡不能用中序、後序,因為你必須先有父節點才能訪問它的子節點。所以父節點必須先被創造出來。

BinaryTree::BinaryTree(const BinaryTree& other_root) {
    if (other_root.root == NULL) {root = NULL; return;}
    root = copyNode(other_root.root, root);
}

下面就是copy遞迴函式:

BinaryTree::Node* BinaryTree::copyNode(BinaryTree::Node * oldNode, BinaryTree::Node * newNode) {
    if (oldNode) {
        newNode = new Node(oldNode->val);
        newNode->left = copyNode(oldNode->left, newNode->left);
        newNode->right = copyNode(oldNode->right, newNode->right);
        return newNode;
    } else {return NULL;}
}

【注意】對比一下兩種情況:
newNode->left = copyNode(oldNode->left, newNode->left);
newNode->right = copyNode(oldNode->right, newNode->right);

copyNode(oldNode->left, newNode->left);
copyNode(oldNode->right, newNode->right);
即,要記得return,因為你傳入進去的只是一個新建立的,作為傳進去元素的一個副本,你在副本上做的操作,並不會影響原來的那個元素。
所以要記得把新建立的newNode return還給父節點的left和right。

(2)、creat a tree by a array.(using BFS)
即根據一個數組通過BFS的訪問順序來建立一棵二叉樹。
所謂的BFS訪問,即是一層一層訪問下去。先訪問level0、再訪問level1、level2……訪問物件可以是圖、樹等資料結構。
做BFS訪問的主要工具是queue,通過push、front、pop的交替使用,來實現逐層訪問。(深度搜索對應stack)
下面是程式碼實現:

BinaryTree::BinaryTree(vector<char> & array) {
    if (array.size() == 0) {root = NULL; return;}
    if (array[0] == '#') {root = NULL; return;}
    int i = 0;
    queue<BinaryTree::Node**> Q;
    BinaryTree::Node** temp;
    root = NULL;
    Q.push(&root);
    while (i != array.size() && !(Q.empty())) {
        temp = Q.front(), Q.pop();
        if (array[i] == '#') {
            *temp = NULL;
            i++;
        } else {
            *temp = new Node(array[i++]);
            Q.push(&((*temp)->left));
            Q.push(&((*temp)->right));
        }
    }
}

這裡有一個要注意的問題,觀察比較上面程式碼和下面程式碼:

BinaryTree::BinaryTree(vector<char> & array) {
    if (array.size() == 0) {root = NULL; return;}
    if (array[0] == '#') {root = NULL; return;}
    int i = 0;
    queue<BinaryTree::Node*> Q;
    BinaryTree::Node* temp;
    root = NULL;
    Q.push(root);
    while (i != array.size() && !(Q.empty())) {
        temp = Q.front(), Q.pop();
        if (array[i] == '#') {
            temp = NULL;
            i++;
        } else {
            temp = new Node(array[i++]);
            Q.push(temp->left);
            Q.push(temp->right);
        }
    }
}

一個是Node*型別的佇列,一個是Node**型的佇列。前者無法實現目的,後者可以。
為什麼呢?
因為每次push進佇列的是Node指標的一個一毛一樣的副本,而不是Node指標它真身。所以你將那個副本front出來給temp,再在temp上面操作,是不會對真身產生任何影響的。
這裡有兩個可以解決的思路,一個是先賦值在push進去。
另一個是用指標的指標,即Node**,這樣便可以直接訪問Node指標,並且在Node指標上面做修改。(比如說改變它指向的元素的數值資訊)。

(3)、creat a tree by preorder and inorder.用先序和中序建立二叉樹。
一個先序的數列並不能唯一的確定一棵二叉樹。
三種順序訪問二叉樹而形成的陣列順序如下:
PreOrder(T) = T的根節點 + PreOrder(T的左子樹) + PreOrder(T的右子樹)
InOrder(T) = InOrder(T的左子樹) + T的根節點 + InOrder(T的右子樹)
PostOrder(T) = PostOrder(T的左子樹) + PostOrder(T的右子樹) + T的根節點
如果將他們都看成字串或者字元,則“+”表示將字串連線起來。
通過先序和中序建立二叉樹的思路:
舉例如先序遍歷為DBACEGF,中序遍歷為ABCDEFG。由先序的訪問順序,第一位必定是根節點。這裡就是D,在看中序,中序的訪問特點使得根節點D在中間,D左邊的都是左子樹,D右邊的都是右子樹。這樣我們知道ABC為左子樹,EFG為右子樹。再繼續用遞迴的思想,第二層的左邊的先序遍歷為BAC,中序遍歷為ABC,按照剛剛的思路,B為父節點,B左邊的A是左子樹,C是右子樹。在看看第二層的右邊的先序遍歷為EGF,中序遍歷為EFG,則E為父節點,E的左邊沒有,則說明它的左子樹不存在,FG都為右子樹,繼續下去:
先序GF,中序FG,則父節點為G,F為G的左子樹。
這樣子一棵二叉樹便顯然了。下面是核心程式碼:

BinaryTree::Node* buildTree(const char* pre, const char* in, int sum) {
    if (sum == 0) return NULL;
    char temp = pre[0];                                 //新地址的第一個元素
    BinaryTree::Node* newNode = new BinaryTree::Node(temp);
    int i;
    for (i = 0; i < sum && in[i] != temp; ++i) {}
    int left_lenth = i;
    int right_lenth = sum - i - 1;
    if (left_lenth > 0) newNode->left = buildTree(&pre[1], &in[0], left_lenth);     //注意傳入的是地址,對於這個地址來說,pre[0]就是它的第一個元素
    if (right_lenth > 0) newNode->right = buildTree(&pre[left_lenth + 1],
    &in[left_lenth + 1], right_lenth);
    return newNode;
}

然後是語法上面的:
首先是在類裡面的static函式,它在類裡面,所以不管是不是static,定義的時候,都要加上類的作用域,但是不用再寫static。
然後是static void preOrder(void (*visite)(BinaryTree::Node*), Node*);的第一個引數,void (*visite)(BinaryTree::Node*),這是一個函式指標型別,visite是函式的指標,它接受一個函式,這個函式的返回型別是void 形參是Node*型別。比如main函式裡面的print函式,使用的時候,直接用visite(root)就好。其實就相當於形參和實參的關係。