人工智能实验一:使用搜索算法实现罗马尼亚问题的求解

news2024/11/27 7:42:38

1.任务描述

本关任务:

  • 了解有信息搜索策略的算法思想;
  • 能够运用计算机语言实现搜索算法;
  • 应用A*搜索算法解决罗马尼亚问题;

2.相关知识

A*搜索

  • 算法介绍

A*算法常用于 二维地图路径规划,算法所采用的启发式搜索可以利用实际问题所具备的启发式信息来指导搜索,从而减少搜索范围,控制搜索规模,降低实际问题的复杂度。

  • 算法原理:

A*算法的原理是设计一个代价估计函数:其中 **评估函数F(n)**是从起始节点通过节点n的到达目标节点的最小代价路径的估计值,函数G(n)是从起始节点到n节点的已走过路径的实际代价,函数H(n)是从n节点到目标节点可能的最优路径的估计代价 。

函数 H(n)表明了算法使用的启发信息,它来源于人们对路径规划问题的认识,依赖某种经验估计。根据 F(n)可以计算出当前节点的代价,并可以对下一次能够到达的节点进行评估。

采用每次搜索都找到代价值最小的点再继续往外搜索的过程,一步一步找到最优路径。

3.编程要求

罗马尼亚问题:agent在罗马尼亚度假,目前位于 Arad 城市。agent明天有航班从Bucharest 起飞,不能改签退票。

现在你需要寻找到 Bucharest 的最短路径,在右侧编辑器补充void A_star(int goal,node &src,Graph &graph)函数,使用编写的搜索算法代码求解罗马尼亚问题:

img

4.测试说明

平台会对你编写的代码进行测试:

预期输出:

solution: 0-> 15-> 14-> 13-> 1-> end
cost:418

5.实验过程

下面是关于非补充部分的代码解释:

1.宏定义每个城市名和编号

#define A 0
#define B 1
#define C 2
#define D 3
#define E 4
#define F 5
#define G 6
#define H 7
#define I 8
#define L 9
#define M 10
#define N 11
#define O 12
#define P 13
#define R 14
#define S 15
#define T 16
#define U 17
#define V 18
#define Z 19

2.记录启发函数h数组,即从n节点到目标节点可能的最优路径的估计代价

int h[20] =//从n节点到目标节点可能的最优路径的估计代价
{ 366,0,160,242,161,
178,77,151,226,244,
241,234,380,98,193,
253,329,80,199,374 };

3.定义城市节点的结构体

struct node
{
    int g;                              //从起始节点到n节点的已走过路径的实际代价
    int h;                              //从n节点到目标节点可能的最优路径的估计代价
    int f;                              //代价估计函数
    int name;
    node(int name, int g, int h) {       //构造函数
        this->name = name;
        this->g = g;
        this->h = h;
        this->f = g + h;
    };

    //重载运算符
    bool operator <(const node& a)const { return f < a.f; }
};

4.定义图结构,记录图中各节点和边的信息

class Graph        //图结构
{
public:
    Graph() {
        memset(graph, -1, sizeof(graph));   //图初始化为-1,代表无边
    }

    int getEdge(int from, int to) {       //获取边的开销
        return graph[from][to];
    }

    void addEdge(int from, int to, int cost) {    //新增一条边及其开销
        if (from >= 20 || from < 0 || to >= 20 || to < 0)
            return;
        graph[from][to] = cost;
    }

    void init() {                        //图初始化
        addEdge(O, Z, 71);
        addEdge(Z, O, 71);

        addEdge(O, S, 151);
        addEdge(S, O, 151);

        addEdge(Z, A, 75);
        addEdge(A, Z, 75);

        addEdge(A, S, 140);
        addEdge(S, A, 140);

        addEdge(A, T, 118);
        addEdge(T, A, 118);

        addEdge(T, L, 111);
        addEdge(L, T, 111);

        addEdge(L, M, 70);
        addEdge(M, L, 70);

        addEdge(M, D, 75);
        addEdge(D, M, 75);

        addEdge(D, C, 120);
        addEdge(C, D, 120);

        addEdge(C, R, 146);
        addEdge(R, C, 146);

        addEdge(S, R, 80);
        addEdge(R, S, 80);

        addEdge(S, F, 99);
        addEdge(F, S, 99);

        addEdge(F, B, 211);
        addEdge(B, F, 211);

        addEdge(P, C, 138);
        addEdge(C, P, 138);

        addEdge(R, P, 97);
        addEdge(P, R, 97);

        addEdge(P, B, 101);
        addEdge(B, P, 101);

        addEdge(B, G, 90);
        addEdge(G, B, 90);

        addEdge(B, U, 85);
        addEdge(U, B, 85);

        addEdge(U, H, 98);
        addEdge(H, U, 98);

        addEdge(H, E, 86);
        addEdge(E, H, 86);

        addEdge(U, V, 142);
        addEdge(V, U, 142);

        addEdge(I, V, 92);
        addEdge(V, I, 92);

        addEdge(I, N, 87);
        addEdge(N, I, 87);
    }

private:
    int graph[20][20];              //图数组,用来保存图信息,最多有20个节点
};

5.一些数据结构的定义

bool list[20];                          //用于记录节点i是否在openList集合中
vector<node> openList;                  //扩展节点集合
bool closeList[20];                     //已访问节点集合
stack<int> road;                        //路径
int parent[20];                         //父节点,用于回溯构造路径

1.补充void A_star(int goal, node& src, Graph& graph)函数

主要思想是利用一个估价函数f(n)来评估每个节点n的优先级,f(n)由两部分组成:g(n)表示从起点到节点n的实际代价,h(n)表示从节点n到终点的预估代价。A*算法每次选择f(n)最小的节点进行扩展,直到找到终点或者没有可扩展的节点为止

代码如下:

void A_star(int goal, node& src, Graph& graph)//A*搜索算法
{
    openList.push_back(src);                    //扩展集合加入起始节点
    sort(openList.begin(), openList.end());     //排序扩展集合的节点,以取出代价最小的节点

    while (!openList.empty())
    {
        /********** Begin **********/
        node curNode = openList[0];             //取出扩展集合第一个节点,即代价最小的节点
        if (curNode.name == goal) {             //如果当前节点就是目标节点,则退出
            return;
        }
        openList.erase(openList.begin());       //将当前节点从扩展列表中删除
        closeList[curNode.name] = true;         //将当前节点加入已访问节点
        list[curNode.name] = false;             //标记当前节点已不在扩展集合中

        for (int i = 0; i < 20; i++) {          //开始扩展当前节点,即找到其邻居节点
            if (graph.getEdge(i, curNode.name) == -1) {     //若不是当前节点的邻居节点,跳到下一个节点
                continue;
            }
            if (closeList[i]) {                             //若此节点已加入已访问集合closeList,也跳到下一个节点
                continue;
            }
            int g1 = curNode.g + graph.getEdge(i, curNode.name);    //计算起始节点到当前节点i的g值
            int h1 = h[i];                                          //获得当前节点i的h值
            if (list[i]) {                                          //如果节点i在openList中
                for (int j = 0; j < openList.size(); j++) {
                    if (i == openList[j].name) {                    //首先找到节点i的位置,即j
                        if (g1 < openList[j].g) {                   //如果新的路径的花销更小,则更新
                            openList[j].g = g1;
                            openList[j].f = g1 + openList[j].h;
                            parent[i] = curNode.name;               //记录父节点
                            break;
                        }
                    }
                }
            }
            else {                        //如果节点i不在openList,则将其加入其中(因为扩展时访问了它)
                node newNode(i, g1, h1);                    //创建新节点,其参数已知
                openList.push_back(newNode);                //新节点加入openList中
                parent[i] = curNode.name;                   //记录父节点
                list[i] = true;                             //记录节点i加入了openList
            }
        }
        sort(openList.begin(), openList.end());     //扩展完当前节点后要对openList重新排序
        /********** End **********/
    }
}

首先扩展起始节点,将扩展集合中的节点按照优先级进行排序。接着按照优先级不断扩展扩展集合中的节点,直到找到终点或者没有可扩展的节点为止。

每次扩展首先取出扩展集合第一个节点,判断其是否为目标节点,若是则退出。扩展该节点后需要将其加入已访问集合,并从扩展集合中删除,同时用list数组标记其已扩展。

接着扩展该节点,即寻找其邻居节点。

如果邻居节点在扩展集合中,则查看其更新后代价是否比原本的代价更优,优则更新它。同时记录父节点以用于回溯生成路径

如果邻居节点不在扩展集合中,则将其加入扩展集合中,记录父节点,并用list数组标记为在扩展集合中

每次扩展完节点后都要对扩展集合里的节点进行一次优先级的排序,用于下一个循环来取出当前优先级最高的节点

6.void print_result(Graph& graph)函数

用于打印路径和开销

void print_result(Graph& graph)     //用于打印路径和开销
{
    int p = openList[0].name;       //p即为目标节点
    int lastNodeNum;
    road.push(p);                   //目标节点压入栈中,之后最后才输出
    while (parent[p] != -1)         //不断回溯获得一条完整路径
    {
        road.push(parent[p]);
        p = parent[p];
    }
    lastNodeNum = road.top();       //起始节点
    int cost = 0;                   //总开销
    cout << "solution: ";
    while (!road.empty())           //栈不为空就继续循环
    {
        cout << road.top() << "-> ";
        if (road.top() != lastNodeNum)  //如果栈顶元素不是终点
        {
            cost += graph.getEdge(lastNodeNum, road.top());     //添加花销
            lastNodeNum = road.top();       //更新栈顶元素
        }
        road.pop();     //弹出栈顶元素
    }
    cout << "end" << endl;
    cout << "cost:" << cost;
}

6.完整代码

#include<iostream>
#include<vector>
#include<memory.h>
#include<stack>
#include<algorithm>

#define A 0
#define B 1
#define C 2
#define D 3
#define E 4
#define F 5
#define G 6
#define H 7
#define I 8
#define L 9
#define M 10
#define N 11
#define O 12
#define P 13
#define R 14
#define S 15
#define T 16
#define U 17
#define V 18
#define Z 19

using namespace std;




int h[20] =//从n节点到目标节点可能的最优路径的估计代价
{ 366,0,160,242,161,
178,77,151,226,244,
241,234,380,98,193,
253,329,80,199,374 };


/*
*一个节点结构,node
*/
struct node
{
    int g;                              //从起始节点到n节点的已走过路径的实际代价
    int h;                              //从n节点到目标节点可能的最优路径的估计代价
    int f;                              //代价估计函数
    int name;
    node(int name, int g, int h) {       //构造函数
        this->name = name;
        this->g = g;
        this->h = h;
        this->f = g + h;
    };

    //重载运算符
    bool operator <(const node& a)const { return f < a.f; }
};


class Graph        //图结构
{
public:
    Graph() {
        memset(graph, -1, sizeof(graph));   //图初始化为-1,代表无边
    }

    int getEdge(int from, int to) {       //获取边的开销
        return graph[from][to];
    }

    void addEdge(int from, int to, int cost) {    //新增一条边及其开销
        if (from >= 20 || from < 0 || to >= 20 || to < 0)
            return;
        graph[from][to] = cost;
    }

    void init() {                        //图初始化
        addEdge(O, Z, 71);
        addEdge(Z, O, 71);

        addEdge(O, S, 151);
        addEdge(S, O, 151);

        addEdge(Z, A, 75);
        addEdge(A, Z, 75);

        addEdge(A, S, 140);
        addEdge(S, A, 140);

        addEdge(A, T, 118);
        addEdge(T, A, 118);

        addEdge(T, L, 111);
        addEdge(L, T, 111);

        addEdge(L, M, 70);
        addEdge(M, L, 70);

        addEdge(M, D, 75);
        addEdge(D, M, 75);

        addEdge(D, C, 120);
        addEdge(C, D, 120);

        addEdge(C, R, 146);
        addEdge(R, C, 146);

        addEdge(S, R, 80);
        addEdge(R, S, 80);

        addEdge(S, F, 99);
        addEdge(F, S, 99);

        addEdge(F, B, 211);
        addEdge(B, F, 211);

        addEdge(P, C, 138);
        addEdge(C, P, 138);

        addEdge(R, P, 97);
        addEdge(P, R, 97);

        addEdge(P, B, 101);
        addEdge(B, P, 101);

        addEdge(B, G, 90);
        addEdge(G, B, 90);

        addEdge(B, U, 85);
        addEdge(U, B, 85);

        addEdge(U, H, 98);
        addEdge(H, U, 98);

        addEdge(H, E, 86);
        addEdge(E, H, 86);

        addEdge(U, V, 142);
        addEdge(V, U, 142);

        addEdge(I, V, 92);
        addEdge(V, I, 92);

        addEdge(I, N, 87);
        addEdge(N, I, 87);
    }

private:
    int graph[20][20];              //图数组,用来保存图信息,最多有20个节点
};

bool list[20];                          //用于记录节点i是否在openList集合中
vector<node> openList;                  //扩展节点集合
bool closeList[20];                     //已访问节点集合
stack<int> road;                        //路径
int parent[20];                         //父节点,用于回溯构造路径

void A_star(int goal, node& src, Graph& graph)//A*搜索算法
{
    openList.push_back(src);                    //扩展集合加入起始节点
    sort(openList.begin(), openList.end());     //排序扩展集合的节点,以取出代价最小的节点

    while (!openList.empty())
    {
        /********** Begin **********/
        node curNode = openList[0];             //取出扩展集合第一个节点,即代价最小的节点
        if (curNode.name == goal) {             //如果当前节点就是目标节点,则退出
            return;
        }
        openList.erase(openList.begin());       //将当前节点从扩展列表中删除
        closeList[curNode.name] = true;         //将当前节点加入已访问节点
        list[curNode.name] = false;             //标记当前节点已不在扩展集合中

        for (int i = 0; i < 20; i++) {                      //开始扩展当前节点,即找到其邻居节点
            if (graph.getEdge(i, curNode.name) == -1) {     //若不是当前节点的邻居节点,跳到下一个节点
                continue;
            }
            if (closeList[i]) {                             //若此节点已加入已访问集合closeList,也跳到下一个节点
                continue;
            }
            int g1 = curNode.g + graph.getEdge(i, curNode.name);    //计算起始节点到当前节点i的g值
            int h1 = h[i];                                          //获得当前节点i的h值
            if (list[i]) {                                          //如果节点i在openList中
                for (int j = 0; j < openList.size(); j++) {
                    if (i == openList[j].name) {                    //首先找到节点i的位置,即j
                        if (g1 < openList[j].g) {                   //如果新的路径的花销更小,则更新
                            openList[j].g = g1;
                            openList[j].f = g1 + openList[j].h;
                            parent[i] = curNode.name;               //记录父节点
                            break;
                        }
                    }
                }
            }
            else {                        //如果节点i不在openList,则将其加入其中(因为扩展时访问了它)
                node newNode(i, g1, h1);                    //创建新节点,其参数已知
                openList.push_back(newNode);                //新节点加入openList中
                parent[i] = curNode.name;                   //记录父节点
                list[i] = true;                             //记录节点i加入了openList
            }
        }
        sort(openList.begin(), openList.end());     //扩展完当前节点后要对openList重新排序
        /********** End **********/
    }
}

void print_result(Graph& graph)     //用于打印路径和开销
{
    int p = openList[0].name;       //p即为目标节点
    int lastNodeNum;
    road.push(p);                   //目标节点压入栈中,之后最后才输出
    while (parent[p] != -1)         //不断回溯获得一条完整路径
    {
        road.push(parent[p]);
        p = parent[p];
    }
    lastNodeNum = road.top();       //起始节点
    int cost = 0;                   //总开销
    cout << "solution: ";
    while (!road.empty())           //栈不为空就继续循环
    {
        cout << road.top() << "-> ";
        if (road.top() != lastNodeNum)  //如果栈顶元素不是终点
        {
            cost += graph.getEdge(lastNodeNum, road.top());     //添加花销
            lastNodeNum = road.top();       //更新栈顶元素
        }
        road.pop();     //弹出栈顶元素
    }
    cout << "end" << endl;
    cout << "cost:" << cost;
}

int main()
{
    Graph graph;
    graph.init();
    int goal = B;                           //目标节点B
    node src(A, 0, h[A]);                   //起始节点A
    list[A] = true;
    memset(parent, -1, sizeof(parent));     //初始化parent
    memset(list, false, sizeof(list));      //初始化list
    A_star(goal, src, graph);
    print_result(graph);
    return 0;
}

运行结果:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ukaYZTHr-1678452048946)(C:\Users\86159\AppData\Roaming\Typora\typora-user-images\1678451967482.png)]

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/402730.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

66 - 进程互斥锁的应用示例

---- 整理自狄泰软件唐佐林老师课程 查看所有文章链接&#xff1a;&#xff08;更新中&#xff09;深入浅出操作系统 - 目录 文章目录1. 简单生产消费者问题1.1 具体问题描述1.2 解决方案1.3 简单生产消费者问题模型1.4 编程实验&#xff1a;生产消费者示例2. 多任务读写问题&a…

中国人民大学与加拿大女王大学金融硕士,让这一年有一个骄傲的句号

在中国人民大学与加拿大女王大学金融硕士项目就读的同学&#xff0c;都有一个共同的目标&#xff0c;那就是在就读的这一年能画上一个圆满的句号。当拿到毕业证书的那一刻&#xff0c;所有的付出和努力都是值得的&#xff0c;在这里学习提升各自理论知识与金融服务经验&#xf…

学生信息表

目录 一、功能说明 二、核心思想 三、所用知识回顾 四、基本框架 五、js功能实现部分 一、功能说明 &#xff08;1&#xff09;输入对应的信息&#xff0c;点击录入可以为下面的表格添加一条记录&#xff0c;注意当所填信息不完整时不允许进行提交。 &#xff08;2&…

高校如何通过校企合作/实验室建设来提高大数据人工智能学生就业质量

高校人才培养应该如何结合市场需求进行相关专业设置和就业引导&#xff0c;一直是高校就业工作的讨论热点。亘古不变的原则是&#xff0c;高校设置不能脱离市场需求太远&#xff0c;最佳的结合方式是&#xff0c;高校具有前瞻性&#xff0c;能领先市场一步&#xff0c;培养未来…

解决win10的过度保护导致文件下载不了程序不能打开运行

win7看来大概是要离我们远去了&#xff0c;虽然我们还能看见她的背影&#xff0c;但大势所趋&#xff0c;我们也只能慢慢的接受win10进入到我们的日常生活。但win10很多时候过度的保护却给我们带来了不便。这里列举两个最常见的问题&#xff0c;当然我这里也给出了解决方案。 文…

无线网络渗透测试系列学习(二) - 在VMware中搭建Metasploit靶机的详细步骤以及端口的简单了解

引言&#xff1a; 无线网络渗透测试系列学习目录&#xff1a; 无线网络渗透测试系列学习&#xff08;一&#xff09; - 在Windows系统下使用虚拟机安装Kali Linux操作系统 在上一篇文章中我们讲解了在Windows下如何在VMware虚拟机中安装Kali Linux操作系统和对Kali的简单配置…

vue项目部署到IIS

项目打包 vue 部署包&#xff1a; 项目路径运行npm run build 运行后生成一个dist文件夹&#xff0c;把这个文件夹放到要部署的服务器 IIS 配置 程序 需要用到下面这两个程序进行配置&#xff1a; 如果 IIS 没有 Web平台安装程序&#xff08;上图管理模块第二个&#x…

3月12日 植树节 Arbor Day / Planting Trees Day

"植树节“是一些国家为防止森林过度开伐&#xff0c;激发人们爱林、造林的感情而设立的法定节日。Arbor Day is one day in the year that prevents deforestation,celebrates trees and promotes planting.春天是植树的时间。Spring is the prime time for planting tree…

python3 简单爬虫入门 抓取男神图

主要目的 为 快速爬虫入门 参考&#xff1a;https://blog.csdn.net/c406495762/article/details/72597755 注意编写日期&#xff1a;2023-3-9 如果时间过久&#xff0c;则代码可能会失效&#xff0c;如果失效&#xff0c;可以根据下面的解析过程&#xff0c;手动更新代码。 …

云端Docker搭建ABY库以及本地CLion使用

文章目录ABY的搭建以及使用前言ABY库的下载、安装及测试CLion配置后续杂项项目改名使用其他的库最后ABY的搭建以及使用 前言 仅做记录&#xff0c;仅供参考&#xff0c;不同人有不同的使用方式命令手敲&#xff0c;可能有错&#xff0c;自己辨识勿问&#xff0c;我懂的也不多…

C++ Primer Plus 第6版 读书笔记(6) 第 6 章 分支语句和逻辑运算符

第 6 章 分支语句和逻辑运算符 C是在 C 语言基础上开发的一种集面向对象编程、泛型编程和过程化编程于一体的编程语言&#xff0c;是C语言的超集。本书是根据2003年的ISO/ANSI C标准编写的&#xff0c;通过大量短小精悍的程序详细而全面地阐述了 C的基本概念和技术&#xff0c;…

从 Clickhouse 到 Apache Doris,慧策电商 SaaS 高并发数据服务的改造实践

作者介绍&#xff1a; 马成&#xff0c;慧策 JAVA高级研发工程师慧策&#xff08;原旺店通&#xff09;是一家技术驱动型智能零售服务商&#xff0c;基于云计算 PaaS、SaaS 模式&#xff0c;以一体化智能零售解决方案&#xff0c;帮助零售企业数字化智能化升级&#xff0c;实现…

C++面向对象编程之六:重载操作符(<<,>>,+,+=,==,!=,=)

重载操作符C允许我们重新定义操作符&#xff08;例如&#xff1a;&#xff0c;-&#xff0c;*&#xff0c;/&#xff09;等&#xff0c;使其对于我们自定义的类类型对象&#xff0c;也能像内置数据类型&#xff08;例如&#xff1a;int&#xff0c;float&#xff0c;double&…

Java 最小路径和

最小路径和中等给定一个包含非负整数的 m x n 网格 grid &#xff0c;请找出一条从左上角到右下角的路径&#xff0c;使得路径上的数字总和为最小。说明&#xff1a;每次只能向下或者向右移动一步。示例 1&#xff1a;输入&#xff1a;grid [[1,3,1],[1,5,1],[4,2,1]]输出&…

求“二维随机变量的期望E(X)与方差D(X)”例题(一)

离散型 设随机变量(X,Y)的联合分布律为 X\Y0100.10.210.30.4 (1)求E(X) 先求x的边缘分布律&#xff0c;表格里x0的概率为0.10.2&#xff0c;于是我们可得 X01P0.30.7直接求E(X)即可&#xff0c;得到结果 (2)求E(XY) 直接x与y相乘就行。 记得别乘多了&#xff0c;别的算了又…

代码随想录算法训练营day55 | 动态规划 583. 两个字符串的删除操作 72. 编辑距离

day55583. 两个字符串的删除操作1.确定dp数组&#xff08;dp table&#xff09;以及下标的含义2.确定递推公式3.dp数组如何初始化4.确定遍历顺序5.举例推导dp数组72. 编辑距离1. 确定dp数组&#xff08;dp table&#xff09;以及下标的含义2. 确定递推公式3. dp数组如何初始化4…

idea热部署

Dea 热部署方式汇总&#xff1a;一、开启自动编译修改file->settings->compiler->build project automatically二、开启允许在运行中修改文件按ctrlshifta&#xff0c;搜索Registry双击进去&#xff0c;点击面板搜索running&#xff0c;勾选下面的值&#xff1a;Eclip…

MySQL索引结构分类及其优劣选择详解

什么是索引&#xff1f; 索引&#xff08;index&#xff09;是帮助MySQL高效获取数据的数据结构&#xff08;有序&#xff09;。在数据库系统中&#xff0c;除了存储数据之外&#xff0c;还维护着满足特定查找算法的数据结构&#xff0c;这些数据结构以某种方式引用&#xff0…

Nacos集群设置开机自启动

一、搭建前提需要的环境 -rw-rw-rw-. 1 root root 8491533 Mar 5 20:05 apache-maven-3.3.9-bin.tar.gz -rw-rw-rw-. 1 root root 189815615 Mar 23 2018 jdk-8u162-linux-x64.tar.gz -rw-r--r--. 1 root root 25548 Apr 7 2017 mysql57-community-release-el7-10.n…

HTML、CSS学习笔记7(移动适配:rem、less)

一、移动适配 rem&#xff1a;目前多数企业在用的解决方案vw / vh&#xff1a;未来的解决方案 1.rem&#xff08;单位&#xff09; 1.1使用rem单位设置尺寸 px单位或百分比布局可以实现吗&#xff1f; ————不可以 网页的根字号——HTML标签 1.2.rem移动适配 写法&#x…