【資料結構】構造二叉樹的三種方法
題目:
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)就好。其實就相當於形參和實參的關係。