您的位置:首页 > 理论基础 > 数据结构算法

【数据结构】构造二叉树的三种方法

2016-05-22 15:22 316 查看

题目:

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)就好。其实就相当于形参和实参的关系。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息