叠甲:本文题解仅代表个人见解,非官方答案,大家有更好想法可以分享出来参考,大家如果有啥意见或者纠错都可以向我反映,我会及时查看意见并进行修改。
解题环境:VS2022
语言:C++
任务描述:基于上一关中构建的林草中药材知识图谱,输入一个中药材信息,遍历知识图谱,从该信息出发依次输出具有该信息的中药材的和药材、茎、果期、花期、果、花、叶、高度和类别(没有的属性则不输出)的问答序列,一个信息可能对应多种中药材,需要全部输出。
解题思路:和20题基本一致,由于构建的邻接表是双向的,20题为其一个方向遍历的应用,本题为其相反方向遍历的应用。除了SearchHerbsByEntity函数,其他与20题一样即可(对于UDG可以用不同算法写)。
关于其相反方向的理解:
依旧是以20题为例,当运行到CreatUDG时,读入第一行数据:
"人参 类别 人参多年生草本。"
此时会新建两个结点与邻接表进行链接,20题只展示了P1结点,现在把两个结点都与邻接表链接起来进行展示如下。
这里我用红色进行对比,如P1之前所说,adjvex=1161表示其在邻接表VNode数组中的索引为1161,所以我们找到下标为1161的结点,其entity=1表示在"类别"在Entity数组中的位置,info为"人参多年生草本"表示其具体内容,firstarc指向我们新建的P2;P2的ad指向0,因为读取一行新建两个结点,两个结点的adjvex互相指向其根结点在邻接表VNode数组中的索引,一个是1161另一个是0,其r=0是两结点的关系是相互的,共同拥有一个关系,nextarc指向NULL。
之后的结点依次类推即可。
所以我们可以通过链接的P1结点的adjvex去读取VNode中其他结点的内容,也可以反过来通过P2结点的adjvex去读取,因此说是相反方向。
SearchHerbsByEntity函数的实现
如上面所说,我们输入一个中药材信息值,然后利用locate函数可以找到其在邻接表G的VNode数组中的索引,之后遍历其链接的边结点即可遍历所有的中药材。
为啥可以遍历到中药材?
因为每个信息构成的三元组的组成是:
中药材名 - 关系 - 具体内容
我们输入的中药材信息对应三元组中的具体内容,我们在CreatUDG函数里面建立了具体内容与中药材名的关系,因此可以通过具体内容得到中药材名。
//三元组
人参 类别 人参多年生草本。
//分别对应 中药材名 - 关系 - 具体内容
为啥可以遍历到所有的中药材?
这里虽然我们在CreatAdjList的时候对于同样的信息,比如"果期为7~9月份。"创建了不同的结点,但是在CreatUDG进行链接时,我们的locate函数只返回了在VNode数组中出现的第一个该信息的结点的索引,所以之后所有的三元组都会链接在这第一个结点上,对于后面具体信息同样是"果期为7~9月份。"的结点不会进行链接。
int LocateVex(ALGraph& G, string str) {
for (int i = 0; i < G.vexnum; i++) {
if (G.vertices[i].info == str) {//顺序遍历 找到第一个符合的结点就返回 因此后面都是链接在第一个符合的结点上
return i;
}
}
return -1; // 未找到
}
具体实现:
我们先找到输入中药材信息的对应索引,用locate函数,并进行错误传入检测。
int startVex = LocateVex(G, entityName);
if (startVex == -1) {
cout << "未找到对应的实体:" << entityName << endl;
return;
}
之后遍历与其相连的所有边结点,记录每个边结点对应的adjvex的值,由于输出的顺序要按照entity.txt文件中我们读取药材时药材的种类顺序一致,而我们如果顺用20题的CreatUDG函数的话,由于其采用的是头插法,那么我们读取时的顺序与其输出时的顺序相反,不过好在这个顺序是有序的,因此可以选择用一个set容器去帮我们自动排序即可,最终我们存入后set容器会帮我们以从小到大的顺序自动排好序刚好符合我们的输出顺序(当然其他算法也行)。
set<int> herbIndices;//定义set结构接受每个药材的索引
ArcNode* pArc = G.vertices[startVex].firstarc;//由于遍历所有边结点
while (pArc != nullptr) {
int adjVex = pArc->adjvex;
herbIndices.insert(adjVex);//set的元素存入用insert成员函数
pArc = pArc->nextarc;//迭代更新
}
之后进行遍历输出即可,set容器的遍历可以用迭代器,也可以直接用一个auto自动推导。由于我们在CreatUDG中对于邻接表中VNode结点的info为药材的结点链接信息的顺序就符合我们要的输出顺序,所以也是直接遍历所有的边结点进行打印即可。
for (auto herbIdx : herbIndices) {//遍历set容器 herbIdx为其遍历得到的内容 这里对于一个int值
string herbName = G.vertices[herbIdx].info;//得到对应的VNode结点的info内容
for (ArcNode* p = G.vertices[herbIdx].firstarc; p; p = p->nextarc) {
// 打印顶点信息、关系类型索引和指向的顶点信息
cout << G.vertices[herbIdx].info << "->"
<< Relationship[p->relationship] << "->"
<< G.vertices[p->adjvex].info << endl;
}
}
函数完整代码如下:
void SearchHerbsByEntity(ALGraph& G, const string& entityName) {
int startVex = LocateVex(G, entityName);
if (startVex == -1) {
cout << "未找到对应的实体:" << entityName << endl;
return;
}
// 查找所有与果期7-9月关联的药材
set<int> herbIndices;
ArcNode* pArc = G.vertices[startVex].firstarc;
while (pArc != nullptr) {
int adjVex = pArc->adjvex;
herbIndices.insert(adjVex);
pArc = pArc->nextarc;
}
// 输出每个药材及其所有属性
for (auto herbIdx : herbIndices) {//遍历set容器 herbIdx为其遍历得到的内容 这里对于一个int值
string herbName = G.vertices[herbIdx].info;//得到对应的VNode结点的info内容
for (ArcNode* p = G.vertices[herbIdx].firstarc; p; p = p->nextarc) {
// 打印顶点信息、关系类型索引和指向的顶点信息
cout << G.vertices[herbIdx].info << "->"
<< Relationship[p->relationship] << "->"
<< G.vertices[p->adjvex].info << endl;
}
}
}