根据题目要求不同,建一颗树有以下几种方式
按给定序列依次插入节点建树
适用BST按非叶节点的孩子节点信息建树
适用一般树按中序遍历序列+其他一种遍历序列建树
适用于二叉树
给定序列插入建数
适用BST
实现
//这种情况适合使用 双链表 结构
//因此结顶结构定义为
struct Node
{
int data;
shared_ptr<Node> left_child, right_child;
Node(int _d = -1):
data(_d),
left_child(nullptr),
right_child(nullptr)
{ }
};
//建树的过程就是不断插入节点的过程
//插入节点操作如下
void Insert(shared_ptr<Node> &root, int data)
{
if (root == nullptr)
{
root = make_shared<Node>(data);
return;
}
else if (data >= root->data)
{
Insert(root->right_child, data);
}
else
{
Insert(root->left_child, data);
}
}
shared_ptr<Node> Create( vector<int>::iterator b, vector<int>::iterator e)
{
shared_ptr<Node> root;
for (auto it = b; it != e; it++)
{
Insert(root, *it);
}
return root;
}
根据孩子节点信息建树
适用所有树
这种情况一般用下标来索引节点,适合用 静态储存结构 来建树,也就是用数组
如 PAT A1053
实现
//使用静态储存
//用一个vector储存孩子节点的下标
struct Node
{
int weight;
vector<int> child;
}Tree[maxn];
//N是总节点的个数
//M是非叶节点的个数
cin >> N >> M >> S;
//读入每个点的权值信息
for (size_t i = 0; i < N; i++)
{
cin >> Tree[i].weight;
}
//读入每个非叶节点的孩子节点信息
while (M--)
{
int index, child_count;
cin >> index >> child_count;
for (size_t i = 0; i < child_count; i++)
{
int child_index;
cin >> child_index;
Tree[index].child.push_back(child_index);
}
}
根据中序+一种其他遍历序列建树
适用二叉树
根据中序遍历(inorder)的序列以及 前序(preorder)、后续(postorder)、层序(sequence order) 三者中的任一种可以建立一颗二叉树
中序+前序
算法思想
- 对于前序序列,第一个总是根节点,而中序序列中根节点在序列中间,将序列划分为左右两边,左边是左子树的节点,右边是右子树节点,由此可以知道左右子树的节点数
- 前序序列的剩余序列中,前面部分是左子树的节点,后面部分是右子树的节点
- 不断递归至序列长度为0
实现
//树节点定义,用双链表
struct Node
{
int data;
shared_ptr<Node> left_child;
shared_ptr<Node> right_child;
Node(int _data = -1) :
data(_data),
left_child(nullptr),
right_child(nullptr)
{ }
};
//####根据前序和中序序列建树###
shared_ptr<Node> Create(int preL, int preR, int inL, int inR)
{
//序列长度为0,退出递归
if (preL > preR)
{
return nullptr;
}
//前序序列的第一位是根节点
int root_index = preL;
shared_ptr<Node> root(new Node(preOr[root_index]));
//在中序序列中找到根节点的位置
int k = inL;
for (; k <= inR; k++)
{
if (inOr[k] == postOr[root_index])
{
break;
}
}
//根据中序中根节点的位置划分左右子树的大小
int num_leftTree = k - inL;
int num_rightTree = inR - k;
//向左右子树递归
//【递归的参数容易搞错,建议画一个简易树和序列对照看】
root->left_child = Create(preL+1 , preL+num_leftTree, inL, inL + num_leftTree - 1);
root->right_child = Create(preR - num_rightTree , postR-num_rightTree+1, inR - num_rightTree + 1, inR);
return root;
}
中序+后续
算法思想
和前序+中序建树一致,不同在于后续序列的最后一个节点是根节点
实现
//####根据后续和中序序列建树###
shared_ptr<Node> Create(int postL, int postR, int inL, int inR)
{
//序列长度为0,退出递归
if (postL > postR)
{
return nullptr;
}
//后续序列的最后一位是根节点
int root_index = postR;
shared_ptr<Node> root(new Node(postOr[root_index]));
//在中序序列中找到根节点的位置
int k = inL;
for (; k <= inR; k++)
{
if (inOr[k] == postOr[root_index])
{
break;
}
}
//根据中序中根节点的位置划分左右子树的大小
int num_leftTree = k - inL;
int num_rightTree = inR - k;
//向左右子树递归
root->left_child = Create(postL , postL + num_leftTree-1 , inL, inL + num_leftTree - 1);
root->right_child = Create(postR - num_rightTree , postR-1, inR - num_rightTree + 1, inR);
return root;
}
中序+层序
我们都知道中序+层序可以建一颗二叉树,但算法书一般跳过这里不讲,题目中也不常见到,但原理还是相似的,实现起来也很简单。
算法思想
对照这个图讲一下算法思想
- 中序的作用仍然是划分左右子树
依次将层序序列插入一个空树
这并不是一个BST,插入时如何知道插入的位置呢?
答案是根据中序序列来确定
模拟这个过程:1.建立一个空树 root
2.将层序第一个A插入root,此时root是空,直接插入
3.插入第二个 B,root不为空
4.在中序中搜索B,发现B在其父节点(A)的左侧
5.将B插入A的左子树
6.直到将层序序列中所有节点插入完毕
实现
//插入函数
void Insert(shared_ptr<Node> &root, char data)
{
if (root == nullptr)
{
root = make_shared<Node>(data);
return;
}
//根据data在中序中出现在其父节点的左边还是右边来确定插往左子树还是右子树
auto it_data = find(begin(inOr), end(inOr), data);
auto it_p_data = find(begin(inOr), end(inOr), root->data);
if (it_data < it_p_data)
{
Insert(root->left_child, data);
}
else
{
Insert(root->right_child, data);
}
}
shared_ptr<Node> Create(int b, int e)
{
shared_ptr<Node> root;
for (auto it = b; it != e; it++)
{
Insert(root, seqOr[it]);
}
return root;
}
用中序、层序建树后,前序、后续遍历的结果