目录
前言:
在线性结构中,每个结点只有一个后继。在有些情况下,一个结点可能有多个后继。树(tree)可以描述这类非线性结构,二叉树(Binary Tree)的递归定义如下:二叉树要么为空,要么由根节点(root)、左子树(left subrree)和右子树(right subtree)组成,而左子树和右子树分别是一颗二叉树。注意,在计算机中,树一般是“倒置”的,即根在上,叶子在下。本文将介绍二叉树的创建及层序遍历。
提示:以下是本篇文章正文内容,代码注释较为详细,可供参考
一、问题描述
输入一颗二叉树,你的任务是按从上到下、从左到右的顺序输出各个结点的值。每个结点都按照从根节点到它的移动序列给出(L表示左,R表示右)。在输入中,每个结点的左括号和右括号之间没有空格,相邻结点之间用一个空格隔开。每棵树的输用一对空括号“()”结束(这对括号本身不代表一个结点),如图所示:
注意,如果从根到某个叶节点的路径上有的结点没有在输入中给出,或者给出超过一次,应当输出-1。结点个数不超过256。
样例输入:
(11,LL) (7,LLL) (8,R) (5,) (4,L) (13,RL) (2,LLR) (1,RRR) (4,RR) ()
(3,L) (4,R) ()
样例输出:
5 4 8 11 13 4 7 2 1
-1
二、创建思路及遍历方法
受之前小球下落问题的启发,是否可以把树上的结点编号,然后把二叉树储存在数组中呢?很遗憾这样的方法在此是行不通的。题目中已限制结点最多有256个。如果各个结点形成一条链,最后一个结点的编号将是巨大的!
所以,需要采用动态结构,根据需要建立新的结点,然后将其组织成一颗树。首先,先读入数据,这里采用的是string函数库里的getline。getline的函数格式:getline(cin,string对象)。getline的作用是读取一整行,直到遇到换行符才停止读取,期间能读取像空格、Tab等的空白符。 之后使用迭代器逐个分析数据,遇到数字则使用readNum函数提取。当识别到 ‘ ,’ 时便开始读取其位置信息直到遇到 ‘ )’ 代表此结点读取结束,然后通过addNode函数创建该结点。这样一来,输入和建树部分便以完成。
之后根据输出需求,从上往下,从左至右,选择层序遍历。此处使用一个队列来完成这个任务,在头文件中加入queue的头文件,初始时只有一个根节点入列,然后每次取出一个结点,就把它的左右子结点(如果存在)放入队列。
对于之前示例的二叉树,层序遍历的过程应是:
队列:5 (5入列)
队列:4 8 (5出列,4、8入列)
队列:8 11 (4出列,11入列)
队列:11 13 4 (8出列,13、4入列)
队列:7 2 13 4 (11出列,7、2入列)
队列:2 13 4 (7出列,无子节点无入列)
队列:13 4 (2出列,同理无入列)
队列:4 (13出列,无入列)
队列:1 (4出列,无入列,最后1出列,队列空完成遍历)
这样遍历二叉树的方法称为宽度优先遍历(Breadth-First Search,BFS)。
为了处理非正常输入的情况(某个叶节点值没有给出,或者给出超过一次),使用一个bool类型对象show来表示此输出的状态,每次开始时重置为true,若有上述情况则变为false,不使用BFS输出结果,而是输出-1。每次完成一次建树遍历后,便销毁该树,以防对后面再次建树遍历产生影响,清除采用的是递归的方法。
三、代码实现
1、二叉树的建立
(1)结点建立
和之前线性结构不同,树结点一般有多个指针,二叉树结点为指向左子节点和右子节点的两个指针。
struct Node {
int val = -1; // 初始化结点值为-1,用来判断是否有结点值未给
Node* left = nullptr, * right = nullptr;
};
Node* root = nullptr; // 定义根节点
(2)新节结点建立
使用new关键字时注意内存的回收,避免内存泄漏。
Node* newNode() { return new Node;} // 创建新结点
(3)新增结点
void addNode(int value, string loc) {
Node* temp = root; // 创建临时指针用于建立新结点
for (int i = 0; i < loc.length(); i++) {
if(loc[i]=='L'){
if (temp->left == nullptr) {
temp->left = newNode(); // 如果该结点的左子结点为空则新建
}
temp = temp->left; // 往左子节点移动
}
else if (loc[i] == 'R') {
if (temp->right == nullptr) {
temp->right = newNode(); // 如果该结点的右子结点为空则新建
}
temp = temp->right; // 往右子节点移动
}
}
if (temp->val != -1) {
show = false; // 若该结点已经给过值,则返回-1并禁止遍历
}
else {
temp->val = value; // 将该节点赋值
}
}
(4) 移除树
void remove(Node* temp) {
if (temp == nullptr) {
return; // 根节点为空则返回
}
remove(temp->left);
remove(temp->right);
delete temp;
temp = nullptr; // 递归遍历清除
}
2.层序遍历
(1)读取数据
double readNum(string::const_iterator& it) {
string temp;
while (*it >= '0' && *it <= '9') {
temp += *it++;
}
return stod(temp); // 将string转化为int读取结点值
}
(2)遍历
void bfs() {
queue<Node*> temp; // 创建队列用于广搜
temp.push(root); // 根节点入列
if (temp.front()->val == -1) {
cout << -1; // 判断根节点是否有值,若没有则返回-1并停止遍历
return;
}
while (!temp.empty()) {
Node* tptr = temp.front();
temp.pop();
cout << tptr->val << " ";
if (tptr->left != nullptr) temp.push(tptr->left);
if (tptr->right != nullptr) temp.push(tptr->right);
} // 层序遍历
}
3.主程序
int main(){
string line;
while (getline(cin, line)) {
root = new Node; // 每次创建二叉树前新创建根节点
show = true; // 重置判断是否为正常输出
string temp; // 临时对象用于存储getline读取的内容
double value = -1; // 给结点值默认为-1,以便判断是否有未给的结点值
for (auto it = line.begin(); it != line.end(); it++) {
if (*it >= '0' && *it <= '9') {
value=readNum(it);
} // 遇到结点值直接读取
if (*it == ',' && *(it+1) == ')') {
root->val = value;
} // 判断是否为(n,)根节点,是则将n赋值给根节点
else if (*it == ',') {
while (*(it+1) != ')') {
it++;
temp.push_back(*it);
} // 识别','并从此开始读取后面的位置信息遇到直到')'代表结束
if (value == -1) {
cout << -1 << endl;
show = false;
} // 如果位置信息读完了但没有结点值(还是默认值-1)则说明是非正常输出,即为-1
addNode(value, temp); // 创建结点
temp.clear(); // 创建完一个结点后将临时对象清空
value = -1; // 将重置默认值
}
if (*it == '(' && *(it + 1) == ')') {
if (show) {
bfs();
cout << endl;
} // 识别'()'代表结束,判断此时若为正常输出则层序遍历并输出
}
}
remove(root); // 完成一次二叉树创建遍历后清空
}
return 0;
}
4.输出示例
此为题目所给测试数据:
四、完整代码
#include <iostream>
#include <string>
#include <queue>
using namespace std;
bool show = true;
struct Node {
int val = -1; // 初始化结点值为-1,用来判断是否有结点值未给
Node* left = nullptr, * right = nullptr;
};
Node* root = nullptr; // 定义根节点
Node* newNode() { return new Node;} // 创建新结点
void addNode(int value, string loc) {
Node* temp = root; // 创建临时指针用于建立新结点
for (int i = 0; i < loc.length(); i++) {
if(loc[i]=='L'){
if (temp->left == nullptr) {
temp->left = newNode(); // 如果该结点的左子结点为空则新建
}
temp = temp->left; // 往左子节点移动
}
else if (loc[i] == 'R') {
if (temp->right == nullptr) {
temp->right = newNode(); // 如果该结点的右子结点为空则新建
}
temp = temp->right; // 往右子节点移动
}
}
if (temp->val != -1) {
show = false; // 若该结点已经给过值,则返回-1并禁止遍历
}
else {
temp->val = value; // 将该节点赋值
}
}
void bfs() {
queue<Node*> temp; // 创建队列用于广搜
temp.push(root); // 根节点入列
if (temp.front()->val == -1) {
cout << -1; // 判断根节点是否有值,若没有则返回-1并停止遍历
return;
}
while (!temp.empty()) {
Node* tptr = temp.front();
temp.pop();
cout << tptr->val << " ";
if (tptr->left != nullptr) temp.push(tptr->left);
if (tptr->right != nullptr) temp.push(tptr->right);
} // 层序遍历
}
double readNum(string::const_iterator& it) {
string temp;
while (*it >= '0' && *it <= '9') {
temp += *it++;
}
return stod(temp); // 将string转化为int读取结点值
}
void remove(Node* temp) {
if (temp == nullptr) {
return; // 根节点为空则返回
}
remove(temp->left);
remove(temp->right);
delete temp;
temp = nullptr; // 递归遍历清除
}
int main(){
string line;
while (getline(cin, line)) {
root = new Node; // 每次创建二叉树前新创建根节点
show = true; // 重置判断是否为正常输出
string temp; // 临时对象用于存储getline读取的内容
double value = -1; // 给结点值默认为-1,以便判断是否有未给的结点值
for (auto it = line.begin(); it != line.end(); it++) {
if (*it >= '0' && *it <= '9') {
value=readNum(it);
} // 遇到结点值直接读取
if (*it == ',' && *(it+1) == ')') {
root->val = value;
} // 判断是否为(n,)根节点,是则将n赋值给根节点
else if (*it == ',') {
while (*(it+1) != ')') {
it++;
temp.push_back(*it);
} // 识别','并从此开始读取后面的位置信息遇到直到')'代表结束
if (value == -1) {
cout << -1 << endl;
show = false;
} // 如果位置信息读完了但没有结点值(还是默认值-1)则说明是非正常输出,即为-1
addNode(value, temp); // 创建结点
temp.clear(); // 创建完一个结点后将临时对象清空
value = -1; // 将重置默认值
}
if (*it == '(' && *(it + 1) == ')') {
if (show) {
bfs();
cout << endl;
} // 识别'()'代表结束,判断此时若为正常输出则层序遍历并输出
}
}
remove(root); // 完成一次二叉树创建遍历后清空
}
return 0;
}