CCF-201809-3-元素选择器

博客内容介绍了如何模拟实现CSS元素选择器,主要涉及树结构的构建、查询遍历和匹配细节。作者通过整理输入将结构化文档转化为树结构,并在查找过程中采用层次遍历,自底向上匹配选择器。文章强调了标签大小写的处理和一个导致错误的根节点问题,最终修复并获得满分解决方案。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题目很长,大家自行去官网看。
第三题还是一如既往的是大模拟,模拟css元素选择器,有接触过前端的同学对此不陌生了吧。
以前学css的时候就想过层叠样式表的实现,但是也没细究。ccf第三题有出过markdown转html的,我就预测ccf还会再出前端类的题目,那时候猜可能会是css,没想到真的出了。这些题外话了,还是讲回题目吧,这题难度不算高,但是细节决定成败。

思路:
结点类:
包含该结点的行数,标签名,id名,父结点(前继结点,存储父结点的意义是为了后面的查询,从后往前找,比从前往后找好,你想想每个结点有多个子节点,但只有唯一父结点,从前往后要找出对应的序列得嵌套多少次循环啊)和子节点列表

1.整理输入:先把输入的结构化文档由结点连成一棵树。每个结点的父结点是从上一行的结点开始往根部搜,搜到第一个比自己层数小1的结点。

2.查找:每个查询都进行一次树的遍历(我用的是层次遍历),找到对应的选择器就开始往上找父元素。注意,选择器从后往前匹配,比如 div a h1, 就在遍历的时候找h1元素,从h1开始匹配,再往上找父元素a, div。再注意,这里的父元素,是所有祖先元素,往上一级找不到就继续往上一级找,直到选择器组都匹配完就记录这个结点的行数,次数加1。

3.细节:标签在结构化文档中和查询的选择器中大小写都有可能,而标签名大小写不敏感,所以遇到结构化文档的标签和查询选择器的标签都要统一化为小写,便于匹配。

新改动:
之前代码有个很难找的BUG,才90,现在终于找到问题所在了,终于AC100。这还得感谢我的一个朋友。
问题出在我的根结点,我的根结点的标签是“root”,理论上这个root不应该被匹配到,而且刚刚好ccf的测试用例的选择器也有root,所以选择上了。答案应该是0才对。解决方法:把根节点的标签(label)名换一个就是了,干脆空字符串"".

Java代码:

import java.util.ArrayList;
import java.util.Comparator;
import java.util.LinkedList;
import java.util.Scanner;

public class 元素选择器 {
    static Scanner input = new Scanner(System.in);
    //static Node root = new Node(0, null, "root", "");  修改前 90
    static Node root = new Node(0, null, "", ""); //修改后 100

    public static void main(String[] args) {
        int n = input.nextInt();
        int m = input.nextInt();

        //接收结构化文档,并整理好结构
        input.nextLine();
        getDateToTree(n);

        //对每个选择器进行匹配,输出匹配结果
        printAns(m);
    }

    //接收结构化文档,并整理成树结构
    public static void getDateToTree(int n) {
        for (int i = 0; i < n; i++) {
            String[] line = input.nextLine().split(" ");
            int dotCount = 0;
            int idx = 0;
            for (int j = 0; j < line[0].length() && line[0].charAt(j) == '.'; j++) {
                dotCount++;
                idx++;
            }

            //把标签转为小写
            String label = line[0].substring(idx, line[0].length()).toLowerCase();
            String id = line.length == 2 ? line[1] : "";

            //指针每次从根结点开始找,找dotCount/2次
            Node pointer = root;
            for (int j = 0; j < dotCount / 2; j++)
                pointer = pointer.subNodes.get(pointer.subNodes.size() - 1);
            pointer.subNodes.add(new Node(i + 1, pointer, label, id));
        }
    }

    //对每个选择器进行匹配,输出匹配结果
    public static void printAns(int m) {
        for (int i = 0; i < m; i++) {
            //该列表存储的是选择器匹配到的标签们在结构化文档中的行数
            ArrayList<Integer> lineNums = new ArrayList<>();
            String[] line = input.nextLine().split(" ");
            match(lineNums, line);
            lineNums.sort(Comparator.comparing(Integer::byteValue));
            System.out.print(lineNums.size());
            for (int lineNum : lineNums)
                System.out.print(" " + lineNum);
            System.out.println();
        }
    }

    //选择器匹配,层次遍历结构树,对每个结点进行匹配
    public static void match(ArrayList<Integer> lineNums, String[] line) {
        LinkedList<Node> queue = new LinkedList<>();
        queue.offer(root);
        while (!queue.isEmpty()) {
            Node tem = queue.poll();

            //从选择器最底端开始匹配
            //如果选择器是标签就改为小写
            String seleter = line[line.length - 1].charAt(0) == '#' ?
                    line[line.length - 1] : line[line.length - 1].toLowerCase();
            if (seleter.equals(tem.label) || seleter.equals(tem.id)) {
                int lineIdx = line.length - 2;
                Node pointer = tem.preNode;
                //从最下级开始往上找父元素
                while (pointer != null && lineIdx >= 0) {
                    //如果选择器是标签就改为小写
                    seleter = line[lineIdx].charAt(0) == '#' ?
                            line[lineIdx] : line[lineIdx].toLowerCase();
                    if (pointer.label.equals(seleter) ||
                            pointer.id.equals(seleter)) {
                        lineIdx--;
                    }
                    pointer = pointer.preNode;
                }
                if (lineIdx == -1)
                    lineNums.add(tem.lineNum);
            }

            //子结点入队
            for (Node val : tem.subNodes)
                queue.offer(val);
        }
    }

}

//标签元素结点类
class Node {
    int lineNum;//元素所在的行数
    Node preNode;
    ArrayList<Node> subNodes = new ArrayList<>();//子结点列表
    String label;
    String id;

    Node(int lineNum, Node preNode, String label, String id) {
        this.lineNum = lineNum;
        this.preNode = preNode;
        this.label = label;
        this.id = id;
    }
}

python代码:

# 标签类
class Node:
    def __init__(self, label, id, fa, level, line_num):
        self.label = label
        self.id = id
        self.fa = fa
        self.son = []
        self.level = level
        self.line_num = line_num


def bfs(selector):
    queue = [root]
    last = selector[-1]
    idxs = []
    while queue:
        cur_node = queue.pop(0)
        # 广搜搜到选择器的最后一个选择器,从这里开始往父结点走,匹配
        if (cur_node.label == last or cur_node.id == last) and match(selector[:], cur_node):
            idxs.append(cur_node.line_num)
        for son in cur_node.son:
            queue.append(son)
    return sorted(idxs)


# 看是否匹配到选择器
def match(selector, cur_node):
    while cur_node.fa and selector:
        if cur_node.label == selector[-1] or cur_node.id == selector[-1]:
            selector.pop()
        cur_node = cur_node.fa
    return False if selector else True


n, m = map(int, input().split())
root = Node('', '', None, -1, 0)
pre_node = root
# 接受数据,整理结构化文档
for i in range(n):
    line = input()
    level = line.count('.')
    line = line[level:].split()
    level /= 2
    label = line[0].lower()  # 标签转小写
    id = line[1] if len(line) == 2 else ''
    fa_node = pre_node
    while fa_node.level != level - 1:  # 找父结点,往前找等级小于自己1的结点
        fa_node = fa_node.fa
    nd = Node(label, id, fa_node, level, i + 1)
    fa_node.son.append(nd)
    pre_node = nd

for i in range(m):
    selector = [e if e[0] == '#' else e.lower() for e in input().split()]  # 标签转小写
    idxs = bfs(selector)
    print(len(idxs), end=' ')
    [print(e, end=' ') for e in idxs]
    print()

# 测试id大小写敏感
# 11 7
# html
# ..head
# ....title
# ..body
# ....h1
# ....p #subtitle
# ....div #main
# ......h2
# ......p #one
# ......div
# ........p #two
# p
# #Subtitle
# h3
# div p
# div div p
# html
# root


# 11 7
# html
# ..head
# ....title
# ..body
# ....h1
# ....p #subtitle
# ....div #main
# ......h2
# ......p #one
# ......div
# ........p #two
# p
# #subtitle
# h3
# DIV p
# div div p
# html
# root

# 11 11
# html
# ..head
# ....title
# ..body
# ....h1
# ....p #subtitle
# ....div #main
# ......h2
# ......p #one
# ......div
# ........p #two
# p
# #subtitle
# h3
# DIV p
# div div p
# html
# root
# #subtitle #main
# #subtitle div
# #subtitle h2
# #main h2

# 11 2
# html
# ..head
# ....title
# ..body
# ....h1
# ....p #subtitle
# ....div #main
# ......h2
# ......p #one
# ......div
# ........p #two
# #main #one
# #main h2

# 12 4
# html
# ..head
# ....title
# ..body
# ....h1
# ....p #subtitle
# ....div #main
# ......h2
# ......p #one
# ......div
# ........p #two
# html
# #main #one
# #main h2
# html
# html head

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值