目录
问题A:二叉链表存储二叉树
题目描述
树形结构是一类重要的非线性数据结构,其中以树和二叉树最为常用。对于每一个结点至多只有两棵子树的一类树,称其为二叉树。二叉树的链式存储结构是一类重要的数据结构,其形式定义如下:
而二叉树的前序、中序遍历是非常重要的能够访问二叉树所有结点的算法,下面分别列出一种先序遍历和两种中序遍历的算法。
第一种中序遍历的方法(算法6.3):
第二种中序遍历的方法(算法6.2):
通过读入一个字符串,建立二叉树的算法如下:
在本题中,将会给出一个按照先序遍历得出的字符串,空格代表空的子节点,大写字母代表节点内容。请通过这个字符串建立二叉树,并按照题目描述中的一种先序遍历和两种中序遍历的算法分别输出每一个非空节点。
输入
输入只有一行,包含一个字符串S,用来建立二叉树。保证S为合法的二叉树先序遍历字符串,节点内容只有大写字母,且S的长度不超过100。
输出
共有三行,每一行包含一串字符,表示分别按先序、中序、中序得出的节点内容,每个字母后输出一个空格。请注意行尾输出换行。
样例输入 复制
ABC DE G F
样例输出 复制
A B C D E G F
C B E G D F A
C B E G D F A
提示
遍历是二叉树各种操作的基础,可以在遍历的过程中对节点进行各种操作。通过二叉树的遍历,可以建立二叉树。而先序、中序和后序遍历分别具有各自的特点,是探索二叉树性质的绝佳“武器”。
#include <iostream>
#include <string>
using namespace std;
struct Node {
char data; // 节点内容
Node* left; // 左子节点指针
Node* right; // 右子节点指针
Node(char d): data(d), left(NULL), right(NULL) {} // 构造函数
};
Node* buildTree(string& s, int& i) { // 根据先序遍历字符串建立二叉树,并返回根节点指针
if (i >= s.size() || s[i] == ' ') return NULL; // 如果字符串结束或遇到空格,返回NULL
Node* root = new Node(s[i]); // 创建根节点
i++; // 移动字符串索引
root->left = buildTree(s, i); // 递归建立左子树,并连接左子节点指针
i++; // 移动字符串索引
root->right = buildTree(s, i); // 递归建立右子树,并连接右子节点指针
return root; // 返回根节点指针
}
void preOrder(Node* root) { // 先序遍历树,并输出结果
if (root == NULL) return; // 如果节点为空,返回
cout << root->data << " "; // 输出当前节点的内容和空格
preOrder(root->left); // 递归遍历左子树
preOrder(root->right); // 递归遍历右子树
}
void inOrder1(Node* root) { // 中序遍历树,并输出结果(第一种方法)
if (root == NULL) return; // 如果节点为空,返回
inOrder1(root->left); // 递归遍历左子树
cout << root->data << " "; // 输出当前节点的内容和空格
inOrder1(root->right); // 递归遍历右子树
}
void inOrder2(Node* root) { // 中序遍历树,并输出结果(第二种方法)
if (root == NULL) return; // 如果节点为空,返回
inOrder2(root->left); // 递归遍历左子树
cout << root->data << " "; // 输出当前节点的内容和空格
inOrder2(root->right); // 递归遍历右子树
}
int main() {
string s; // 先序遍历字符串
getline(cin, s); // 输入先序遍历字符串,用getline函数可以读入空格
int i = 0; // 字符串索引,初始为0
Node* root = buildTree(s, i); // 根据先序遍历字符串建立二叉树,并得到根节点指针
preOrder(root); // 先序遍历树,并输出结果
cout << endl; // 输出换行符
inOrder1(root); // 中序遍历树,并输出结果(第一种方法)
cout << endl; // 输出换行符
inOrder2(root); // 中序遍历树,并输出结果(第二种方法)
cout << endl; // 输出换行符
return 0; // 程序结束
}
问题B:最小生成树
题目描述
最小生成树问题是实际生产生活中十分重要的一类问题。假设需要在n个城市之间建立通信联络网,则连通n个城市只需要n-1条线路。这时,自然需要考虑这样一个问题,即如何在最节省经费的前提下建立这个通信网。
可以用连通网来表示n个城市以及n个城市之间可能设置的通信线路,其中网的顶点表示城市,边表示两个城市之间的线路,赋于边的权值表示相应的代价。对于n个顶点的连通网可以建立许多不同的生成树,每一棵生成树都可以是一个通信网。现在,需要选择一棵生成树,使总的耗费最小。这个问题就是构造连通网的最小代价生成树,简称最小生成树。一棵生成树的代价就是树上各边的代价之和。
而在常用的最小生成树构造算法中,普里姆(Prim)算法是一种非常常用的算法。以下是其算法的大致结构:
在本题中,读入一个无向图的邻接矩阵(即数组表示),建立无向图并按照以上描述中的算法建立最小生成树,并输出最小生成树的代价。
输入
输入的第一行包含一个正整数n,表示图中共有n个顶点。其中n不超过50。
以后的n行中每行有n个用空格隔开的整数,对于第i行的第j个整数,如果不为0,则表示第i个顶点和第j个顶点有直接连接且代价为相应的值,0表示没有直接连接。当i和j相等的时候,保证对应的整数为0。
输入保证邻接矩阵为对称矩阵,即输入的图一定是无向图,且保证图中只有一个连通分量。
输出
只有一个整数,即最小生成树的总代价。请注意行尾输出换行。
样例输入 复制
4
0 2 4 0
2 0 3 5
4 3 0 1
0 5 1 0
样例输出 复制
6
提示
在本题中,需要掌握图的深度优先遍历的方法,并需要掌握无向图的连通性问题的本质。通过求出无向图的连通分量和对应的生成树,应该能够对图的连通性建立更加直观和清晰的概念。
#include <iostream>
#include <vector>
using namespace std;
const int INF = 0x3f3f3f3f; // 定义一个无穷大的常量,用于表示没有直接连接的情况
struct Edge {
int from; // 边的起点
int to; // 边的终点
int cost; // 边的代价
Edge(int f, int t, int c): from(f), to(t), cost(c) {} // 构造函数
};
int prim(vector<vector<int>>& graph, vector<Edge>& tree) { // 根据邻接矩阵构建最小生成树,并返回总代价
int n = graph.size(); // 图中顶点的个数
vector<int> pre(n, -1); // 前驱数组,存储每个顶点的前驱顶点的索引,初始为-1
vector<bool> visited(n, false); // 访问标记数组,初始为false
vector<int> dist(n, INF); // 距离数组,存储每个顶点到当前生成树的最短距离,初始为无穷大
int total = 0; // 总代价,初始为0
dist[0] = 0; // 从第一个顶点开始构建最小生成树,将其距离设为0
for (int i = 0; i < n; i++) { // 循环n次,每次加入一个顶点到最小生成树中
int u = -1; // 用于寻找距离最小的顶点的索引,初始为-1
int minDist = INF; // 用于记录最小距离,初始为无穷大
for (int j = 0; j < n; j++) { // 遍历所有顶点
if (!visited[j] && dist[j] < minDist) { // 如果该顶点没有被访问过,且其距离小于当前最小距离
u = j; // 更新最小距离顶点的索引
minDist = dist[j]; // 更新最小距离
}
}
if (u == -1) return -1; // 如果没有找到合适的顶点,说明图不连通,返回-1
visited[u] = true; // 将找到的顶点标记为已访问
total += minDist; // 将最小距离加入总代价中
if (i > 0) tree.push_back(Edge(u, pre[u], minDist)); // 如果不是第一个顶点,将对应的边加入最小生成树中,pre[u]表示u的前驱顶点
for (int v = 0; v < n; v++) { // 遍历所有顶点,更新距离数组和前驱数组
if (!visited[v] && graph[u][v] < dist[v]) { // 如果该顶点没有被访问过,且u到v的边的代价小于当前距离
dist[v] = graph[u][v]; // 更新距离
pre[v] = u; // 更新前驱
}
}
}
return total; // 返回总代价
}
int main() {
int n; // 图中顶点的个数
cin >> n; // 输入顶点个数
vector<vector<int>> graph(n, vector<int>(n)); // 邻接矩阵,初始为n*n的二维向量
vector<Edge> tree; // 最小生成树,初始为空
for (int i = 0; i < n; i++) { // 输入邻接矩阵
for (int j = 0; j < n; j++) {
cin >> graph[i][j]; // 输入第i行第j列的元素,即i到j的边的代价,如果为0表示没有直接连接
if (graph[i][j] == 0) graph[i][j] = INF; // 将0替换为无穷大,方便后续处理
}
}
int result = prim(graph, tree); // 调用prim函数构建最小生成树,并得到总代价
if (result == -1) { // 如果返回值为-1,说明图不连通
cout << "The graph is not connected." << endl; // 输出提示信息
} else { // 如果返回值不为-1,说明图连通
cout << result << endl; // 输出总代价
}
return 0; // 程序结束
}
问题C:哈夫曼树
题目描述
哈夫曼树,第一行输入一个数n,表示叶结点的个数。需要用这些叶结点生成哈夫曼树,根据哈夫曼树的概念,这些结点有权值,即weight,题目需要输出所有叶子结点的路径长度与权值的乘积之和。
输入
输入有多组数据。
每组第一行输入一个数n,接着输入n个叶节点(叶节点权值不超过100,2<=n<=1000)。
输出
输出权值。
样例输入 复制
2
2 8
3
5 11 30
样例输出 复制
10
62
#include <iostream>
#include <queue>
#include <vector>
using namespace std;
//定义一个结点类,包含权值和左右子结点
class Node {
public:
int weight;
Node* left;
Node* right;
Node(int w) {
weight = w;
left = NULL;
right = NULL;
}
};
//定义一个比较函数,用于优先队列的排序
struct cmp {
bool operator()(Node* a, Node* b) {
return a->weight > b->weight; //权值小的优先
}
};
//计算哈夫曼树的权值和
int huffmanSum(Node* root, int depth) {
if (root == NULL) return 0; //空结点返回0
if (root->left == NULL && root->right == NULL) return root->weight * depth; //叶子结点返回权值乘以深度
return huffmanSum(root->left, depth + 1) + huffmanSum(root->right, depth + 1); //非叶子结点递归计算左右子树的和
}
int main() {
int n; //叶结点个数
while (cin >> n) { //多组输入
priority_queue<Node*, vector<Node*>, cmp> pq; //创建一个优先队列,用于存储结点指针
for (int i = 0; i < n; i++) {
int w; //输入权值
cin >> w;
Node* node = new Node(w); //创建一个新的结点
pq.push(node); //将结点指针入队
}
while (pq.size() > 1) { //当队列中还有多于一个结点时,循环执行以下操作
Node* left = pq.top(); //取出队首的最小权值结点,作为左子结点
pq.pop(); //出队
Node* right = pq.top(); //取出队首的次小权值结点,作为右子结点
pq.pop(); //出队
Node* parent = new Node(left->weight + right->weight); //创建一个新的父结点,其权值为左右子结点的和
parent->left = left; //连接左子结点
parent->right = right; //连接右子结点
pq.push(parent); //将父结点入队
}
Node* root = pq.top(); //最后队列中只剩一个结点,即为哈夫曼树的根结点
cout << huffmanSum(root, 0) << endl; //输出哈夫曼树的权值和,初始深度为0
}
return 0;
}
问题D:二叉树问题
题目描述
现给定一棵二叉树的先序遍历序列和中序遍历序列,要求你计算该二叉树的高度。
输入
输入包含多组测试数据,每组输入首先给出正整数N(<=50),为树中结点总数。下面2行先后给出先序和中序遍历序列,均是长度为N的不包含重复英文字母(区别大小写)的字符串。
输出
对于每组输入,输出一个整数,即该二叉树的高度。
样例输入 复制
9
ABDFGHIEC
FDHGIBEAC
7
Abcdefg
gfedcbA
样例输出 复制
5
7
#include <iostream>
#include <string>
using namespace std;
//定义一个结点类,包含数据和左右子结点
class Node {
public:
char data;
Node* left;
Node* right;
Node(char d) {
data = d;
left = NULL;
right = NULL;
}
};
//根据先序和中序遍历序列构建二叉树,返回根结点
Node* buildTree(string pre, string in) {
if (pre.empty() || in.empty()) return NULL; //如果序列为空,返回空指针
char rootData = pre[0]; //先序遍历的第一个字符是根结点的数据
Node* root = new Node(rootData); //创建一个新的根结点
int pos = in.find(rootData); //在中序遍历中找到根结点的位置
string leftIn = in.substr(0, pos); //中序遍历中根结点左边的子串是左子树的中序遍历
string rightIn = in.substr(pos + 1); //中序遍历中根结点右边的子串是右子树的中序遍历
string leftPre = pre.substr(1, leftIn.size()); //先序遍历中除去根结点后与左子树中序遍历长度相同的子串是左子树的先序遍历
string rightPre = pre.substr(leftIn.size() + 1); //先序遍历中剩余的子串是右子树的先序遍历
root->left = buildTree(leftPre, leftIn); //递归构建左子树,连接到根结点的左指针
root->right = buildTree(rightPre, rightIn); //递归构建右子树,连接到根结点的右指针
return root; //返回根结点
}
//计算二叉树的高度,返回一个整数
int treeHeight(Node* root) {
if (root == NULL) return 0; //如果为空树,返回0
int leftHeight = treeHeight(root->left); //递归计算左子树的高度
int rightHeight = treeHeight(root->right); //递归计算右子树的高度
return max(leftHeight, rightHeight) + 1; //返回左右子树高度较大者加一,即为当前树的高度
}
int main() {
int n; //结点个数
while (cin >> n) { //多组输入
string pre, in; //先序和中序遍历序列
cin >> pre >> in; //输入序列
Node* root = buildTree(pre, in); //构建二叉树,得到根结点
cout << treeHeight(root) << endl; //输出二叉树的高度
}
return 0;
}
问题E:完全二叉树
题目描述
给定一棵树,你应该指出它是否是一个完全二叉树。
输入
对于每种情况,第一行给出正整数N(≤20),它是树中节点的总数(节点从0到N-1编号)。 然后是N行,第i行对应一个节点i,并给出节点i左右子节点的索引。 如果孩子不存在,则 - 将被置于该位置。
输出
对于每种情况,如果树是完全二叉树,则在一行中打印YES和层序遍历的最后一个节点的索引,或者如果不是,则打印NO和根的索引。 必须有一个空格分隔单词和数字。
样例输入 复制
9
7 8
- -
- -
- -
0 1
2 3
4 5
- -
- -
样例输出 复制
YES 8
#include<iostream>
#include<queue>
using namespace std;
struct Node{
char left,right;
};
int main(){
int n;
cin>>n;
Node* ntr=new Node[n];
bool* root=new bool[n];
for(int i=0;i<n;i++) root[i]=true;
char x,y;
for(int i=0;i<n;i++){
cin>>x>>y;
ntr[i].left=x;
ntr[i].right=y;
if(x!='-') root[(int)(ntr[i].left)-48]=false;
if(y!='-') root[(int)(ntr[i].right)-48]=false;
}
queue<char> Q;
for(int i=0;i<n;i++){
if(root[i]){
int flag=0;
Q.push((char)(i+48));
char fro=Q.front();
char last='-';
while(fro!='-'){
Q.push(ntr[(int)(fro)-48].left);
Q.push(ntr[(int)(fro)-48].right);
Q.pop();
last=fro;
fro=Q.front();
}
while(!Q.empty()){
if(Q.front()!='-'){
flag--;
break;
}else{
Q.pop();
}
}
flag++;
if(flag==0){
cout<<"NO "<<i<<endl;
}else{
cout<<"YES "<<last<<endl;
}
}
}
return 0;
}
问题F:树的遍历
题目描述
假设二叉树中的所有键值都是不同的正整数。唯一的二元树可以通过给定的后序和顺序遍历序列,或前序和顺序遍历序列来确定。但是,如果仅给出后序和前序遍历序列,则相应的树可能不再是唯一的。
现在给出一对后序和前序遍历序列,您应该输出树的相应的中序遍历序列。如果树不是唯一的,只需输出其中任何一个。
输入
每个输入文件包含一个测试用例。对于每种情况,第一行给出正整数N(≤30),即二叉树中的节点总数。第二行给出预订序列,第三行给出后序序列。一行中的所有数字都用空格分隔。
输出
对于每个测试用例,如果树是唯一的,则首先是行中的Yes,否则是No。然后在下一行中打印相应二叉树的中序遍历序列。如果解决方案不是唯一的,那么任何答案都可以。保证至少存在一种解决方案。一行中的所有数字必须用一个空格分隔,并且行的末尾不能有额外的空格。
样例输入 复制
7
1 2 3 4 6 7 5
2 6 7 4 5 3 1
样例输出 复制
Yes
2 1 6 4 7 3 5
#include <iostream>
#include <vector>
using namespace std;
vector<int> in, pre, post;
bool unique = true;
void getIn(int preLeft, int preRight, int postLeft, int postRight) {
if(preLeft == preRight) {
in.push_back(pre[preLeft]);
return;
}
if (pre[preLeft] == post[postRight]) {
int i = preLeft + 1;
while (i <= preRight && pre[i] != post[postRight-1]) i++;
if (i - preLeft > 1)
getIn(preLeft + 1, i - 1, postLeft, postLeft + (i - preLeft - 1) - 1);
else
unique = false;
in.push_back(post[postRight]);
getIn(i, preRight, postLeft + (i - preLeft - 1), postRight - 1);
}
}
int main() {
int n;
scanf("%d", &n);
pre.resize(n), post.resize(n);
for (int i = 0; i < n; i++)
scanf("%d", &pre[i]);
for (int i = 0; i < n; i++)
scanf("%d", &post[i]);
getIn(0, n-1, 0, n-1);
printf("%s\n%d", unique == true ? "Yes" : "No", in[0]);
for (int i = 1; i < in.size(); i++)
printf(" %d", in[i]);
printf("\n");
return 0;
}