CCF 201809-3 2018年9月第三题元素选择器(C++ 100分题解)
试题编号: 3
试题名称: 元素选择器
时间限制: 1.0s
内存限制: 512.0MB
提交后100分代码:
这道题的大致思路:首先用stringstream对输入的字符串进行分词,然后进行一系列处理(可以直接用if逻辑硬刚,也可以用正则表达式)获得需要的属性,包括标签名、id属性名、节点所在层级等。然后建树,将节点全部插入树中。最后对询问进行反馈,关键是对分词处理后的询问的字符串序列和每个节点(可以用层序遍历、先序遍历、后序遍历)的从根节点到它的路径进行比较。
这道题本身很简单,但是我刚做的时候很多东西都不会,正好借这个题目学习了很多。
第一,对于字符串的处理,要用到stringstream进行分词,可以用正则表达式进行匹配;
第二,数据结构,这其实就是一个简单的普通的多叉树,用层序遍历(随便哪种遍历都行)按照题目要求查找指定的节点即可。
题目里面有些要注意的地方,比如标签选择器大小写不敏感,匹配时都转成小写。
```cpp
#include
#include
#include
#include
#include
#include
#include
using namespace std;
string nameOfParent[100];//用来存放每一个层级的最后一个加入的节点的标签名 name。
struct node{//对树节点的定义
int lineNum, level, parentLevel; //lineNum是节点输入的行数,其实就是节点的序号;level是根据每行输入的前部的"."的数量进行等级
//的评定,决定该节点在第几层;parentLevel是该节点的父节点的level值。
string name, idName, parentName; // name是每行输入的标签,idName是每行输入的id属性,parentName是父亲节点的name值。
vector child; //child是每个节点的所有子节点的地址组成的一个数组。
vector path; //path是由 从这棵树的根节点一直到该节点这一途径中 所有节点 的地址 组成的一个数组。
node* parent; //该节点的父节点的地址。
};
node* createTreeNode(int lineNum, int level, int parentLevel, string name, string idName, string parentName, node* parent) {//创建树节点
node* Node = new node;
Node->lineNum = lineNum;
Node->level = level;
Node->parentLevel = parentLevel;
Node->name = name;
Node->idName = idName;
Node->parentName = parentName;
Node->child.clear();
Node->path.clear();
Node->parent = parent;
if(parent != NULL) { //如果父节点不为空,将父节点的path数组复制到该节点的path数组。
for(int i=0; iparent->path.size(); ++i)
Node->path.push_back(Node->parent->path[i]);
}
Node->path.push_back(Node); //将该节点本身加入到该节点的path数组的末尾。
return Node;
}
void insertTreeNode(node* &root, int lineNum, int level, int parentLevel, string name, string idName, string parentName) {//插入树节点
if(root == NULL) { //在只有这一棵树的题目中,这个if语句就是针对根节点
root = createTreeNode(lineNum, level, parentLevel, name, idName, parentName, NULL);
return ;
}
//如果该节点不是根节点,而是一般的节点,就先遍历他的所有子节点
for(int i=0; ichild.size(); ++i) { //!!!我把这个for循环调整到了下面这个if语句上面,因为下面这个if语句判断完之后会加入新的孩子节点,再进行for循环,遇到该节点和父亲节点同名时会导致一直重复这两个步骤
insertTreeNode(root->child[i], lineNum, level, parentLevel, name, idName, parentName);
}
if(root->name == parentName && root->level == parentLevel) {//!!!debug发现的错误,因为同一层不会有同名的,所以用层级和名字同时判断来进行锁定,否则可能会出现该节点的名称和父亲节点名称相同的情况
root->child.push_back(createTreeNode(lineNum, level, parentLevel, name, idName, parentName, root));
}
}
bool match(node* root, stack strStack) {//对于每一个节点,判断他的path数组的序列是否和strStack中存放的序列(即输入的询问请求)相符。
int len = root->path.size();
for(int i=len-1; i>=0; --i) {
node* temp = root->path[i];
if(strStack.empty()) //如果strStack序列能按照顺序完全和path数组的序列相符,那么strStack的所有元素一定会被逐次全部弹出,strStack最后会成为空栈。
return true;
string str = strStack.top();
if(str[0] == '#') {//是id属性
if(temp->idName == str.substr(1, str.size()-1))
strStack.pop();
else {
if(i == len-1) //!!!我是从序列的后端开始比对两个序列的元素是否相符,所以如果从后数第一个元素就不符合就要直接退出。
return false;
}
}
else{// 是标签
if(temp->name == str)
strStack.pop();
else {
if(i == len-1)
return false;
}
}
}
if(strStack.empty())
return true;
else //如果此时strStack不是空栈,说明strStack的序列(即输入的询问请求)与该节点的path数组的序列不按照顺序相符。
return false;
}
void search(node* root, stack strStack, set &output) { //层序遍历,对每个节点是否符合 strStack的序列(即输入的询问请求)进行判断
queue q;
q.push(root);
while(!q.empty()) {
node* currentNode = q.front();
if(match(currentNode, strStack))
output.insert(currentNode->lineNum);
q.pop();
for(int i=0; ichild.size(); ++i) {
if(currentNode->child[i] != NULL)
q.push(currentNode->child[i]);
}
}
}
int main () {
int n, m;
cin >> n >> m;
string tab;
getline(cin, tab);
node* root = NULL; //初始化树
for(int i=1; i<=n; ++i) { //第一部分:数据处理,通过stringstream进行分词,通过正则表达式对需要的数据进行提取
string str, temp, name, idName = "noIdName", parentName = "noParentName";
int level, parentLevel = -1;
getline(cin, str);
stringstream ss;
ss << str;
queue q;
while(ss >> temp) {
if(temp[0] == '.') {//是标签
regex re("[.]*");
smatch result;
if(regex_search(temp, result, re)) {
level = result.str().length() / 2;
regex re2("[a-zA-Z0-9]{1,}");
smatch result2;
if(regex_search(temp, result2, re2))
name = result2.str();
for(int j=0; j output; //第二部分:对输入的查询请求进行反馈
stack strStack;
for(int i=0; i> temp) {
if(temp[0] != '#') {
for(int j=0; j::iterator it=output.begin(); it!=output.end(); ++it) {
cout << " " << *it;
}
cout << endl;
}
return 0;
}