所有程序为笔者个人所写,仅供参考。
第一题(必做题)
题面
在森林上定义一种特殊的遍历次序,称为H遍历。

规则如下:对于有多棵子树的森林,先遍历第一棵子树T1,再遍历第二棵子树T2,依次按顺序遍历完所有子树。在每一棵子树内部,比如T1的内部,先遍历T1的第一棵子树,再访问T1的根结点,再遍历T1的其余的子树森林。
如图所示,该树的H遍历结果是:EBFACIHJKGD。
因为任意的树都可以转换为对应的二叉树,如上图所示的树,可以转换成下图所示的二叉树:

请设计一个算法,能够在以二叉链表表示的树(即孩子兄弟链结构)上输出H遍历次序。
具体要求如下:
(1) 定义二叉树的结点的数据类型,并使用动态内存分配的方式向二叉树中插入新的结点。(5分)
(2) 定义栈元素的数据类型,以满足遍历二叉链表结构的需要。概念上是一个二叉树的结点入栈,同时应该表明该结点是其父结点的左孩子还是右孩子,这样可以方便判断回溯时是从左子树上来的还是从右子树上来的。(5分)
(3) 要求该算法是非递归算法,不允许使用递归。在算法中可以使用自定义的栈,也可以使用C/C++库中的栈。(5分)
(4) 不允许将二叉链表结构改造成一般树的孩子链结构,也就是说,不允许在树上进行H遍历,而是要直接在对应的二叉树上进行H遍历。(5分)
(5) 算法执行完毕,要以正确的方式释放动态申请的空间;(5分)
(6) 在遍历二叉树的过程中,遇到左子树为空,而右子树不空的结点,要同时输出该结点及其父结点(如连续输出E、B)。(5分)
(7) 在遍历二叉树的过程中,一旦对一个结点进行退栈操作,意味着要连续对一系列的结点进行退栈操作。条件包括右子树为空或者右子树已经处理完毕,或者左子树已经处理完毕且右子树为空。(5分)
(8) 当一个结点退栈,且是从非空的左子树回溯时,如果结点的子结点没有右孩子,应当输出该结点(如输出G)。(5分)
(9) 在结点连续退栈的过程中,如果到达一个从左子树回溯的结点,且其右子树非空,则应输出其父结点的数据(如到达B,输出A)。(5分)
(10) 以本题所示的图中的树为数据,输出正确的H遍历的结果。(5分)
要求main()函数实现下述操作:
int main(){
BTNode *root;
makeTree(root);
HTravserse(root);
freeTree(root);
return 0;
}提示:本题的目的是考查如何显式使用栈来完成树上的特殊次序的深度优先遍历。因此,基本思路是在二叉树上完成深度优先遍历,在下降的过程中完成入栈的操作,在出栈的过程中确认对子树的处理结果。难点在于,一个结点退栈和输出该结点的数据并不总是同步发生的,有些结点在退栈之前就已经输出了,因此要寻找合适的输出结点数据的时机。为了防止结点重复入栈,退栈的操作应该是连续的,或者设计特殊的栈元素结构表明结点的状态。
解答
#include <iostream>
#include <stack>
using namespace std;
typedef struct BTNode {
char data;
struct BTNode* left;
struct BTNode* right;
BTNode(char data, struct BTNode* left, struct BTNode* right) {
this->data = data;
this->left = left;
this->right = right;
}
} BTNode;
void makeTree(BTNode* &root) {
//Hard-encoded nodes
BTNode* r = new BTNode('K',nullptr,nullptr);
r = new BTNode('J',nullptr,r);
r = new BTNode('I',nullptr,r);
r = new BTNode('H',r,nullptr);
r = new BTNode('G',r,nullptr);
r = new BTNode('D',r,nullptr);
r = new BTNode('C',nullptr,r);
BTNode* l = new BTNode('F',nullptr,nullptr);
l = new BTNode('E',nullptr,l);
r = new BTNode('B',l,r);
r = new BTNode('A',r,nullptr);
root = r;
}
typedef struct StElement {
BTNode* node;
int position;
BTNode* parent;
int status;
}StElement;
void HTravserse(BTNode* &root) {
stack<StElement> s;
s.push(StElement{root,1,nullptr,0});
while(!s.empty()) {
StElement &p = s.top();
if (p.node==nullptr) {
if (p.position==0&&p.parent!=nullptr) {
cout<<p.parent->data<<" ";
}
s.pop();
continue;
}
if (p.status == 0) {
s.push(StElement{p.node->left,0,p.node,0});
p.status = 1;
continue;
}
if (p.status == 1) {
if (p.position==0) {
cout<<p.parent->data<<" ";
BTNode* r = p.node->right;
s.pop();
stack<StElement> ss;
while (r!=nullptr) {
ss.push(StElement{r,1,p.node,0});
r = r->right;
}
while (!ss.empty()) {
s.push(ss.top());
ss.pop();
}
} else {
s.pop();
}
}
}
}
void freeTree(BTNode* root) {
if (root==nullptr) return;
freeTree(root->left);
freeTree(root->right);
delete root;
}
int main() {
BTNode* root;
makeTree(root);
HTravserse(root);
freeTree(root);
return 0;
}重点就是栈中的每个节点有两个标记,明确这个就很好写了。
第二题(选做题)
题面
对n 个不同的正整数,进行如下操作:每一次删去其中两个数 a 和 b,然后加入一个新数:a*b+1,如此循环操作直到只剩下一个数。所有按这种操作方式最后得到的数中,最大的为max,最小的为min,计算max-min。
举例如下:假设有三个数 2,4,3。
第一种循环操作:取出4,3,将4*3+1放回序列,得到2,13;2*13+1=27。
第二种循环操作,取出2,3,将2*3+1放回序列,得到4,7;4*7+1=29。
所以,max-min=29-27=2。
定理:每次从序列中取出两个最小的整数,循环操作可以得到max。每次从序列中取出两个最大的整数,循环操作可以得到min。
根据以上定理(定理已经给出,无需证明),编写一个算法得到一个正整数序列上的max-min的值。具体要求如下:
(1) 程序运行时,从键盘上输入序列长度n,以及n个正整数值。(10分)
(2) 采用合适的数据结构,完成对max和min的计算。(20分)
(3) 所采用的数据结构,可以自己编写,也可以使用C/C++中的库函数或者类。(10分)
(4) 输入多个不同的序列,都能够得到正确的结果。(5分)
(5) 使用int类型的变量,序列长度逐渐增加,程序运行会得到错误的结果,是何原因,如何解决?(3分)
(6) 在序列长度为n的情况下,分析算法复杂度。(2分)
解答
#include <iostream>
#include <queue>
using namespace std;
int main() {
int n;
cin>>n;
priority_queue<int,vector<int>,less<>> q_min;
priority_queue<int,vector<int>,greater<>> q_max;
for (int i=0; i<n; i++) {
int input;
cin>>input;
q_min.push(input);
q_max.push(input);
}
while (q_max.size()!=1) {
int a = q_max.top();
q_max.pop();
int b = q_max.top();
q_max.pop();
q_max.push(a*b+1);
}
while (q_min.size()!=1) {
int a = q_min.top();
q_min.pop();
int b = q_min.top();
q_min.pop();
q_min.push(a*b+1);
}
cout<< q_max.top() - q_min.top() <<endl;
return 0;
}大水题,会优先队列就能解。
第三题(选做题)
题面
给定一个后缀表达式,如ab+c*d+e*,将其转换成对应的中缀表达式,如((a+b)*c+d)*e。
为简化编程,所有的运算数直接用单个字母表示,运算符只有+、-、*、/四种。具体要求如下:
(1) 在中缀表达式中,考虑到运算符之间的优先级,在有需要使用括号()时,必须使用;
(2) 没有必要使用括号时,不允许出现多余的括号;
(3) 输入的后缀表达式是一个字符串,输出的中缀表达式也要求是一个字符串;(5分)
(4) 自行选用合适的辅助数据结构,如栈、队列等。这些辅助数据结构可以自行实现,也可以调用C/C++函数库或者类库;(10分)
(5) 正确处理空间的申请和释放;(5分)
(6) 输入的后缀表达式有可能是非法的,要能识别表达式是否非法;(5分)
(7) 正确组织处理流程,输出的结果正确无误;(20分)
(8) 在不同的表达式上都取得正确的结果。(5分)
解答
#include <iostream>
using namespace std;
int parse_operand(string s) {
int ops = 0;
int vals = 0;
for (int i = 0; i < s.length(); i++) {
if (s[i]>='0' && s[i]<='9') {
vals++;
} else if (s[i]=='+' || s[i]=='-' || s[i]=='*' || s[i]=='/') {
ops++;
}
else throw runtime_error("Invalid character!");
if (vals == ops+1) {
return i;
}
}
throw runtime_error("Invalid sequence!");
}
pair<string,char> translate(string s) {
if (s.empty()) throw runtime_error("Invalid syntax!");
char ch = s[0];
if (ch>='0' && ch<='9' && s.size() == 1) {
return {string({ch}),ch};
} else if (s.size()<2) {
throw runtime_error("Invalid syntax!");
} else if (ch=='+' || ch=='-') {
int split = parse_operand(s.substr(1));
return {translate(s.substr(2+split)).first + ch + translate(s.substr(1,1+split)).first,'+'};
} else if (ch=='*' || ch=='/') {
int split = parse_operand(s.substr(1));
pair<string,char> r1 = translate(s.substr(1,1+split));
pair<string,char> r2 = translate(s.substr(2+split));
return {((r2.second=='+' || r2.second == '-') ?"(":"") +
r2.first +
((r2.second=='+' || r2.second == '-') ?")":"") +
ch +
((r1.second=='+' || r1.second == '-') ?"(":"") +
r1.first +
((r1.second=='+' || r1.second == '-') ?")":"")
,'-'};
}
throw runtime_error("Invalid sequence!");
}
int main() {
string s;
cin >> s;
reverse(s.begin(), s.end());
try {
cout<<translate(s).first<<endl;
} catch (exception &e) {
cout << "[ERROR] " << e.what() << endl;
}
return 0;
}没什么特别好说的,就是一个递归,这里的异常处理用的是try-catch。