ZOJ 1011 – NTA 英文题目
《算法分析与设计-以大学生程序设计竞赛为例》
算法分析
1. 样例分析
第一个完全二叉树如图。
参数n=4,m=2表示信号有4个,即0,1,2和3,后面两个信号2和3合法。
从根结点开始是信号0,信号发射单元是a,查表该结点产生信号(1,2),信号1给左,信号2给右,标注在结点的连线上,左子树的发射单元是b,可以产生两种信号:(0,2),(1,0)。程序会将这两种信号都向下遍历一遍,最后取信号(1,0)。重复该过程至所有结点。最后我们看到,所有叶子结点产生的信号,都是合法信号,即都是信号2和3,因此该树是有效的,输出单词 valid。
2. 信号发射表的数据结构
从样例看出,该表不是一个普通的二维表,表中元素的个数是不固定的。很容易想到的办法是建立一个二维数组,其中的元素是链表。这种数据结构是有效的,但使用起来肯定是麻烦的。
使用C++标准模板库的vector容器,可有效地解决数据存储问题。
// 定义信号的结构体
struct signal {
int left, right;
};
// 使用vector容器定义信号发射表
vector<signal>table[20][20];
// push_back()方法存集合元素
signal pair; // 定义signal的变量
scanf("%d%d", &pair.left, &pair.right);
table[i][j].push_back(pair); // 构造集合
3. 信号数据的读取方法
读取信号数据很麻烦,因为不知道一行到底有多少数据。
可以把一行数据当作一个字符串,读取一行,然后再从字符串中读取数据,处理的工作量还是比较大的。实际上可以直接判断回车符,以判定当前行是否结束
实现代码如下。
// ---- 算法1 读取数据,构造信号发射表
void readTable() {
char ch;
for(int i=0; i<n; i++) {
table[i][j].clear();
while(true){
signal pair; // 定义signal的变量
scanf("%d%d", &pair.left, &pair.right); // 读取信号
table[i][j].push_back(pair); // 构造集合
ch = getchar();
if(ch == '\n') break; // 判断回车
}
}
}
4. 完全二叉树的数据结构
从根结点开始对树的结点按深度优先原则进行编号,根据结点编号node。
int left = node*2 + 1;
int right = left + 1;
使用一维数组存储了一颗完全二叉树:
char tree[1000];
树的构造在函数void readTree()中完成。其中treeDeep表示结点编号,从0到9,每读取一个结点,编号加1。当树构造完毕,treeDeep中存放了树的结点总数。
// ---- 算法2 构造完全二叉树
void readTree() {
char ch;
treeDeep = 0; // 树结点从0开始编号
int i, j; // i表示树的每一行,j表示数的每一个结点
// treeLevel 是树的深度
for( i=0; i<=treeLevel; i++ ) { // 树的每一行
for( j=0; j<(1<<i); j++ ) { // 该行中树的每一个结点
cin >> ch; // 自动跳过空格
tree[treeDeep] = ch; // 形成树的结点
treeDeep++; // 产生下一个结点编号
}
}
}
5. 合法性判断
一颗完全二叉树的遍历与搜索,可以使用回溯算法。
在函数 bool judge( int signal, int node)中,signal表示传入该结点node的信号。
对结点node,要检查是不是为空“*”,或者超出总结点数 treeDeep。
(1)如果结点编号node>= treeDeep,表示其父结点是叶子结点,这时需要判断信号是不是合法的。如果是合法的,表示父结点产生的一对信号中,传过来的这个信号是合法的。
(2)对该结点的所有信号发射单元产生的信号逐一进行检查,如果所有的信号使其所到的叶子结点产生的信号都是非法的,则该树就是非法的。
// ---- 算法3 树的合法性判断
// 形参signal表示该结点node的信号
bool judge(int signal, int node) {
int signal1, signal2;
//
if( tree[node]=='*' || node>=treeDeep ) {
//
if( signal<n-m ) return false;
else return true;
}
// 结点的信号发射单元编号
int k1 = tree[node]-'a';
// 该结点的左子树编号
int left = node*2 + 1;
// 该结点的右子树编号
int right = left + 1;
// table[signal][k1].size()是信号发射表中,该结点链表的长度
for(int i=0; i<table[signal][k1].size(); i++) {
signal1 = table[signal][k1][i].left;
signal2 = table[signal][k1][i].right;
if( judge(signal1, left) && judge(signal2, right) ) return true;
}
return false;
}
英文积累
acceptable signals 合法信号
auxiliary n.辅助
non-leaf node 非叶子结点
finite adj.有限的
signal-transmitting 信号发射
substance n.介质
non-deterministically 非确定性,随机
successor n.继承者(这里为子树)
for simplicity 简单起见
consecutive adj.连续的
configuration n.结构
代码实现
/**
题目来源:浙大ZOJ,编号1011,题名"NTA"
URL:http://acm.zju.edu.cn/onlinejudge/showProblem.do?problemCode=1011
Author:Christina
Finish Time:2017.09.26
*/
#include
#include
using namespace std; struct signal // 信号 { int left,right; // 左右子树 }; vector
table[20][20]; // 信号发射表 int n,m,k; // n是信号的个数,m是和发信号的个数,k是信号发射单元个数 int treeLevel; // 树的行数 char tree[1000]; // 定义一个完全二叉树 int treeDeep; // 树结点编号 /** * 信号数据读取的方法 * 可以把一行数据当作一个字符,读取一行,然后再从字符串中读取数据, * 处理的工作量还是比较大的。实际上可以直接判断回车符,以判断当前行是否结束 */ void readTable() { char ch; for(int i=0; i
> ch; // 会自动跳过空格 tree[treeDeep] = ch; // 形成树的一个节点 treeDeep++; // 产生下一个节点 } } /** * 树的合理性判断 * (1)如果节点编号node>=treeDeep,表示其父类节点是叶子节点,这时需要判断信号是否合法。 * 如果合法,表示父节点产生的一对信号中,传过来的信号是合法的 * (2)对该节点的所有信号发射单元产生的信号逐一进行检查,如果所有的信号时期所有达到的叶子节点产生都是非法的, * 则该树就是非法的。 */ bool judge(int signal, int node) // signal表示传入该节点node的信号 { int signal1, signal2; if( tree[node]=='*' || node>=treeDeep ) // 叶子节点的合法性判断 if ( signal