数据结构-难点突破(C++实现图的基本操作(邻接矩阵,邻接表,十字链表法储存代码))

news2024/11/16 19:54:41

关于图的数据结构,我曾经自己学过一部分,图论专栏,但是学习本就是重复的过程,这里打算系统的学习一下图。第一步当然是图的储存和基本操作的实现。

要用C++实现图的基本操作

  1. Adjacent(x,y):判断图是否存在边<x,y>或(x,y)
  2. InsertVertex(x):在图中插入节点x
  3. DeleteVertex(x):在图中删除节点x
  4. AddEdge(x,y):添加边<x,y>或(x,y)
  5. RemoveEdge(x,y):删除边<x,y>或(x,y)
  6. SetEdgeValue(x,y,z):设置边的权值(添加边)
  7. GetNeighborsPoint(x):获取图中顶点x的邻节点
  8. PrintGraph():打印保存图的邻接矩阵

文章目录

  • 1. 邻接矩阵存储图,并实现基本操作
  • 2. 邻接表存储图
  • 3. 十字链表法储存

1. 邻接矩阵存储图,并实现基本操作

简单的来讲就是二维数组保存图基本信息:

每一行代表一个顶点,依次从 a 到 b ,每一列也是如此。比如 _matrix[0][1] = weight ,表示 a 和 b 之间有边存在;而 arcs[0][2] = MAX,说明 V1 和 V3 之间没有边。

对于有向图,这个二位数组不是对称的,对于无向图,这个二维数组就是对称的,可以仅仅保留一半。

//邻接矩阵法存储图结构

#include <iostream>
#include <assert.h>
#include <map>
#include <vector>
#include <stdio.h>

// v:图顶点保存的值。w:边的权值 max:最大权值,代表无穷。flag=true代表有向图。否则就是无向图
template <class v, class w, w max = INT_MAX, bool flag = false>
class graph
{
private:
    std::vector<v> _verPoint;            //顶点集合
    std::map<v, int> _indexMap;          //顶点与下标的映射
    std::vector<std::vector<w>> _matrix; //邻接矩阵

    int _getPosPoint(const v &point)
    {
        if (_indexMap.find(point) != _indexMap.end())
        {
            return _indexMap[point];
        }
        else
        {
            std::cout << point << " not found" << std::endl;
            return -1;
        }
    }

public:
    //根据数组来开辟邻接矩阵
    graph(const std::vector<v> &src)
    {
        _verPoint.resize(src.size());
        for (int i = 0; i < src.size(); i++)
        {
            _verPoint[i] = src[i];
            _indexMap[src[i]] = i;
        }

        //初始化邻接矩阵
        _matrix.resize(src.size());
        for (int i = 0; i < src.size(); i++)
        {
            _matrix[i].resize(src.size(), max);
        }
    }

    //添加边的关系,输入两个点,以及这两个点连线边的权值。
    void AddEdge(const v &pointA, const v &pointB, const w &weight)
    {
        //获取这个顶点在邻接矩阵中的下标
        int posA = _getPosPoint(pointA);
        int posB = _getPosPoint(pointB);
        _matrix[posA][posB] = weight;
        if (!flag)
        {
            //无向图,邻接矩阵对称
            _matrix[posB][posA] = weight;
        }
    }

    //打印邻接矩阵
    void PrintGraph()
    {
        //打印顶点对应的坐标
        typename std::map<v, int>::iterator pos = _indexMap.begin();
        while (pos != _indexMap.end())
        {
            std::cout << pos->first << ":" << pos->second << std::endl;
            pos++;
        }
        std::cout << std::endl;

        //打印边
        printf("  ");
        for (int i = 0; i < _verPoint.size(); i++)
        {
            std::cout << _verPoint[i] << " ";
        }
        printf("\n");

        for (int i = 0; i < _matrix.size(); i++)
        {
            std::cout << _verPoint[i] << " ";
            for (int j = 0; j < _matrix[i].size(); j++)
            {
                if (_matrix[i][j] == max)
                {
                    //这条边不通
                    printf("∞ ");
                }
                else
                {
                    std::cout << _matrix[i][j] << " ";
                }
            }
            printf("\n");
        }
        printf("\n");
    }

    //判断图是否存在边<x,y>或(x,y)
    bool Adjacent(const v &x, const v &y)
    {
        return _matrix[_indexMap[x]][_indexMap[y]] != max;
    }

    //列出图中x相邻的边
    std::vector<v> GetNeighborsPoint(const v &x)
    {
        int index = _indexMap[x];
        assert(index >= 0);
        std::vector<v> result;
        for (int i = 0; i < _matrix[index].size(); i++)
        {
            if (_matrix[index][i] != max)
            {
                // std::cout << x << "->" << _verPoint[i] << std::endl;
                result.push_back(_verPoint[i]);
            }
        }
        return result;
    }

    // 在图中插入节点x
    void InsertVertex(const v &x)
    {
        _verPoint.push_back(x);
        _indexMap[x] = _verPoint.size() - 1;
        for (int i = 0; i < _matrix.size(); i++)
        {
            _matrix[i].push_back(max);
        }
        std::vector<w> newLine(_verPoint.size(), max);
        _matrix.push_back(newLine);
    }

    //在图中删除节点x
    void DeleteVertex(const v &x)
    {
        int pos = _indexMap[x];
        assert(pos >= 0);
        _verPoint.erase(_verPoint.begin() + pos);
        _indexMap.erase(x);
        _matrix.erase(_matrix.begin() + pos);
        for (int i = 0; i < _matrix.size(); i++)
        {
            _matrix[i].erase(_matrix[i].begin() + pos);
        }
    }

    //删除边<x,y>或(x,y)
    void RemoveEdge(const v &x, const v &y)
    {
        //假定x,y存在,减少代码量
        _matrix[_indexMap[x]][_indexMap[y]] = max;
        if (!flag)
        {
            //无向图
            _matrix[_indexMap[y]][_indexMap[x]] = max;
        }
    }

    //设置边的权值(添加边)
    void SetEdgeValue(const v &x, const v &y, const w &z)
    {
        //假定x,y存在,减少代码量
        _matrix[_indexMap[x]][_indexMap[y]] = z;
        if (!flag)
        {
            //无向图
            _matrix[_indexMap[y]][_indexMap[x]] = z;
        }
    }
};

测试代码:

#include "matrix.h"

using namespace std;

int main(int argc, char const *argv[])
{
    vector<char> vet = {'a', 'b', 'c', 'd'};
    graph<char, int> graph(vet);
    graph.AddEdge('a', 'd', 1);
    graph.AddEdge('c', 'b', 1);
    graph.AddEdge('c', 'd', 1);
    graph.PrintGraph();
    std::cout << graph.Adjacent('a', 'd') << " " << graph.Adjacent('a', 'b') << endl;
    vector<char> ret = graph.GetNeighborsPoint('c');
    for (int i = 0; i < ret.size(); i++)
    {
        std::cout << 'c' << "->" << ret[i] << std::endl;
    }
    graph.InsertVertex('e');
    graph.PrintGraph();

    // graph.DeleteVertex('a');
    // graph.PrintGraph();
    graph.RemoveEdge('a', 'd');
    graph.PrintGraph();
    return 0;
}

在这里插入图片描述

2. 邻接表存储图

邻接表存储图的核心思想是:将图中的所有顶点存储到顺序表中(也可以是链表)。
同时为各个顶点配备一个单链表,用来存储和当前顶点有直接关联的边或者弧(边的一端是该顶点或者弧的弧尾是该顶点)。

eg:
在这里插入图片描述
邻接表的优点:

  1. 适合保存稀疏的边关系。
  2. 适合查找一个顶点连接出的边

不足:

  1. 不适合确定两个顶点是否相连,判断权值。

链表节点内的值中保存边执向节点在数组的下标,和这条权值数据。

#include <iostream>
#include <vector>
#include <assert.h>
#include <unordered_map>

template <class w>
struct Edge
{
    int dstPos = -1;
    w weight; //权值
    Edge<w> *next;
    Edge(int _dstPos, const w &_weight) : dstPos(_dstPos), weight(_weight), next(nullptr) {}
};

// v:节点的值,w节点的权值 flag==false为无向图
template <class v, class w, bool flag = false>
class linkTable
{
    typedef Edge<w> Edge;

private:
    std::vector<Edge *> _matrix;          //邻接表
    std::unordered_map<v, int> _indexMap; //保存图节点对应邻接表数组的下标
    std::vector<v> _points;               //顶点集合

    int _getPointPos(const v &point)
    {
        typename std::unordered_map<v, int>::iterator pos = _indexMap.find(point);
        if (pos == _indexMap.end())
            return -1; //没找到
        return pos->second;
    }

public:
    linkTable(const std::vector<v> &src)
    {
        int size = src.size();
        assert(size > 0);
        _points.resize(size);
        for (int i = 0; i < size; i++)
        {
            _points[i] = src[i];
            _indexMap[src[i]] = i;
        }
        _matrix.resize(size, nullptr);
    }

    //添加边的关系
    void AddEdge(const v &src, const v &dst, const w &weight)
    {
        int posSrc = _getPointPos(src);
        int posDst = _getPointPos(dst);
        assert(posSrc >= 0 && posSrc >= 0);

        //构建Edge,头插到数组上
        Edge *edge = new Edge(posDst, weight);
        edge->next = _matrix[posSrc];
        _matrix[posSrc] = edge;

        if (!flag)
        {
            //无向图,两条边都要构建
            edge = new Edge(posSrc, weight);
            edge->next = _matrix[posDst];
            _matrix[posDst] = edge;
        }
    }

    //打印邻接表信息
    void PrintGraph()
    {
        for (int i = 0; i < _matrix.size(); i++)
        {
            Edge *edge = _matrix[i];
            while (edge != nullptr)
            {
                std::cout << _points[i] << "->";
                std::cout << _points[edge->dstPos] << "权值:" << edge->weight << std::endl;
                edge = edge->next;
            }
            std::cout << "--------------------------------" << std::endl;
        }
    }
};

测试代码:

#include "linkTable.h"

int main(int argc, char const *argv[])
{
    std::cout << "无向图" << std::endl;
    linkTable<char, int> graph({'a', 'b', 'c', 'd'});
    graph.AddEdge('a', 'd', 4);
    graph.AddEdge('c', 'd', 2);
    graph.AddEdge('c', 'b', 4);
    graph.PrintGraph();

    std::cout << "+++++++++++++++++++++++++++++++++" << std::endl;
    std::cout << "有向图" << std::endl;
    linkTable<char, int, true> graph2({'a', 'b', 'c', 'd'});
    graph2.AddEdge('a', 'd', 4);
    graph2.AddEdge('c', 'd', 2);
    graph2.AddEdge('c', 'b', 4);
    graph2.PrintGraph();
    return 0;
}


在这里插入图片描述

3. 十字链表法储存

用邻接表存储有向图(网),可以快速计算出某个顶点的出度,但计算入度的效率不高。反之,用逆邻接表存储有向图(网),可以快速计算出某个顶点的入度,但计算出度的效率不高。

为了解决快速计算有向图(网)中某个顶点的入度和出度,这里采用十字链表这种种存储结构。

十字链表(Orthogonal List)是一种专门存储有向图(网)的结构,它的核心思想是:
将图中的所有顶点存储到顺序表(也可以是链表)中,同时为每个顶点配备两个链表,一个链表记录以当前顶点为弧头的弧,另一个链表记录以当前顶点为弧尾的弧。

eg:
在这里插入图片描述

顺序表节点数据定义:图片来源
在这里插入图片描述

#include <iostream>
#include <unordered_map>
#include <vector>
#include <assert.h>

struct Data
{
    int tailpos = -1; //弧尾在顺序表的位置下标
    int headpos = -1; //弧头在顺序表的位置下标
    Data *head_link = nullptr;
    Data *tail_link = nullptr;
    // head_lik指向下一个以当前顶点为弧头的弧结点;(指入这个节点)
    // tail_link 指向下一个以当前顶点为弧尾的弧结点;(指出这个节点)

    int weight; //保存弧权值
};

struct TableNode
{
    char val = 0; //图节点的值
    Data *in = nullptr;
    Data *out = nullptr;
    //指向以该顶点为弧头(指入这个节点)和弧尾(指出这个节点)的链表首个结点
};

class FrontTailList
{
private:
    std::vector<TableNode> _verPoint;        //顶点集合
    std::unordered_map<char, int> _indexMap; //顶点与下标的映射
    std::vector<std::vector<int>> _matrix;   //邻接矩阵

    bool flag = false; //标记这个图是有向还是无向,false默认无向
    bool isDel = false;

    int _getPosPoint(const char point)
    {
        if (_indexMap.find(point) != _indexMap.end())
        {
            return _indexMap[point];
        }
        else
        {
            std::cout << point << " not found" << std::endl;
            return -1;
        }
    }

public:
    FrontTailList(const std::vector<char> &src, bool flag)
    {
        this->flag = flag;
        _verPoint.resize(src.size());
        _matrix.resize(src.size());
        for (int i = 0; i < src.size(); i++)
        {
            _indexMap[src[i]] = i;
            _matrix[i].resize(src.size(), INT_MAX);
        }
    }

    void AddEdge(const char pointA, const char pointB, int weight)
    {
        int indexA = _getPosPoint(pointA);
        int indexB = _getPosPoint(pointB);
        assert(indexA >= 0 && indexB >= 0);
        _matrix[indexA][indexB] = weight;

        Data *link = new Data;
        link->headpos = indexB;
        link->tailpos = indexA;
        link->weight = weight;
        //采用头插法插入新的节点

        // indexB的入度节点就是pointA这个节点,这里选择头插法插入。
        link->head_link = _verPoint[indexB].in;
        link->tail_link = _verPoint[indexA].out;

        _verPoint[indexB].in = link;
        _verPoint[indexA].out = link;

        if (!flag && !isDel)
        {
            //无向图
            isDel = true; //记录这次的无向图,两条边已经处理过了。
            AddEdge(pointB, pointA, weight);
        }
        //退出条件后说明这条边添加完毕,为了下次添加边的时候还可以解决无向图问题,这里将isDel恢复原状
        isDel = false;
    }

    //打印邻接矩阵
    void PrintGraph()
    {
        //打印顶点对应的坐标
        typename std::unordered_map<char, int>::iterator pos = _indexMap.begin();
        while (pos != _indexMap.end())
        {
            std::cout << pos->first << ":" << pos->second << std::endl;
            pos++;
        }
        std::cout << std::endl;

        //打印边
        printf("  ");
        for (int i = 0; i < _verPoint.size(); i++)
        {
            std::cout << _verPoint[i].val << " ";
        }
        printf("\n");

        for (int i = 0; i < _matrix.size(); i++)
        {
            std::cout << _verPoint[i].val << " ";
            for (int j = 0; j < _matrix[i].size(); j++)
            {
                if (_matrix[i][j] == INT_MAX)
                {
                    //这条边不通
                    printf("∞ ");
                }
                else
                {
                    std::cout << _matrix[i][j] << " ";
                }
            }
            printf("\n");
        }
        printf("\n");
    }

    //计算某个点的出度和入度
    int InDegree(const char point)
    {
        int pos = _getPosPoint(point);
        if (pos < 0)
        {
            std::cout << "图中没有这个节点" << std::endl;
            return -1;
        }
        int ret = 0;
        Data *node = _verPoint[pos].in;
        while (node != nullptr)
        {
            ret += 1;
            node = node->head_link;
        }
        return ret;
    }

    int OutDegree(const char point)
    {
        int pos = _getPosPoint(point);
        if (pos < 0)
        {
            std::cout << "图中没有这个节点" << std::endl;
            return -1;
        }
        int ret = 0;

        Data *node = _verPoint[pos].out;
        while (node != nullptr)
        {
            ret += 1;
            node = node->tail_link;
        }
        return ret;
    }
};

#include "FrontTailList.h"

using namespace std;

int main(int argc, char const *argv[])
{
    FrontTailList graph({'a', 'b', 'c', 'd'}, false);
    graph.AddEdge('a', 'd', 1);
    graph.AddEdge('c', 'd', 2);
    graph.AddEdge('c', 'b', 3);

    graph.PrintGraph();
    cout << graph.InDegree('d') << endl;
    cout << graph.OutDegree('d') << endl;

    FrontTailList graph2({'a', 'b', 'c', 'd'}, true);
    graph2.AddEdge('a', 'd', 1);
    graph2.AddEdge('c', 'd', 2);
    graph2.AddEdge('c', 'b', 3);

    graph2.PrintGraph();

    cout << graph2.InDegree('d') << endl;
    cout << graph2.OutDegree('d') << endl;

    return 0;
}

在这里插入图片描述

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

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

相关文章

[附源码]Python计算机毕业设计Django健康医疗体检

项目运行 环境配置&#xff1a; Pychram社区版 python3.7.7 Mysql5.7 HBuilderXlist pipNavicat11Djangonodejs。 项目技术&#xff1a; django python Vue 等等组成&#xff0c;B/S模式 pychram管理等等。 环境需要 1.运行环境&#xff1a;最好是python3.7.7&#xff0c;…

[附源码]计算机毕业设计JAVA校园闲置物品交易

[附源码]计算机毕业设计JAVA校园闲置物品交易 项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybati…

第十五章 如何编写README文档

README 文档对于开源项目的重要性甚至会超过代码本身。你试想一下&#xff0c;你打开一个 Github 项目&#xff0c;第一时间就会看到 README 文档&#xff0c;而这时候同一类的项目你可能有很多选择&#xff0c;如果这个README不正规&#xff0c;无法快速上手&#xff0c;你可能…

4.springboot中整合Mybatis

Springboot整合mybatis 在 SpringSpringMVC 中整合 MyBatis 步骤需要在配置文件里配置多个 Bean&#xff0c;比如MapperScannerConfigurer&#xff0c;SqlSessionFactoryBean 等&#xff0c;步骤还是比较复杂的&#xff0c;Spring Boot 中对此做了进一步的简化&#xff0c;使 …

GIS工具maptalks开发手册(三)03——官网示例之添加图层和移除图层

GIS工具maptalks开发手册(三)03——官网示例之添加图层和移除图层 效果 代码 index.html <!DOCTYPE html> <html> <meta charset"UTF-8"> <meta name"viewport" content"widthdevice-width, initial-scale1"> <ti…

Android系统启动流程

Android系统完整的启动过程&#xff0c;从系统层次角度可分为 Linux 系统层、Android 系统服务层、Zygote进程模型三个阶段&#xff1b;从开机到启动 Home Launcher 完成具体的任务细节可分为七个步骤&#xff0c;下面就从具体的细节来解读 Android 系统完整的初始化过程。 Lo…

java计算机毕业设计基于springboot电商项目(附源码讲解)

目录 1. 用户端 1.1 主页&#xff08;未登录时可以查看商品但是不可以购买&#xff09; 1.2 登录&#xff08;账号密码登录) 1.3 登录&#xff08;手机验证码登录&#xff09; 1.4 注册 1.5 查看商品详情 1.6 将商品加入购物车 1.7 在商品购物车中选中商品购买 1.8 …

Jenkins用户权限配置 (三)

平时开发会分为测试环境、生产环境&#xff0c;多个开发人员需要区分不同的权限。例如普通开发人员只能看到测试视图和发布测试环境&#xff0c;生产环境的发布则由负责把控的人员进行统一发布&#xff0c;所以需要在新建用户的同时也分配好权限 (一) 安装Role-based Authoriz…

基于C#的公交充值管理系统的设计与实现

项目描述 临近学期结束&#xff0c;还是毕业设计&#xff0c;你还在做C#程序网络编程&#xff0c;期末作业&#xff0c;老师的作业要求觉得大了吗?不知道毕业设计该怎么办?网页功能的数量是否太多?没有合适的类型或系统?等等。这里根据疫情当下&#xff0c;你想解决的问题…

利用SCRM进行精细化社群运营

有很多企业凭借私域模式获得爆发式的流量增长&#xff0c;但流量转化仍旧是个问题&#xff0c;因此企业在获得流量的同时&#xff0c;还要守住流量&#xff0c;进行精细化运营才行。 前言 近年来&#xff0c;私域、社群、裂变的模式已成为各行各业进行营销的主旋律&#xff0c…

【Matplotlib绘制图像大全】(八):Matplotlib使用text()添加文字标注

前言 大家好,我是阿光。 本专栏整理了《Matplotlib绘制图像大全》,内包含了各种常见的绘图方法,以及Matplotlib各种内置函数的使用方法,帮助我们快速便捷的绘制出数据图像。 正在更新中~ ✨ 🚨 我的项目环境: 平台:Windows10语言环境:python3.7编译器:PyCharmMatp…

[附源码]计算机毕业设计招聘系统Springboot程序

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybatis Maven Vue 等等组成&#xff0c;B/S模式 M…

GIS工具maptalks开发手册(三)02——层级缩放工具

GIS工具maptalks开发手册(三)02——层级缩放工具 效果-层级缩放工具 代码 index.html <!DOCTYPE html> <html><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1"><title>…

[附源码]Python计算机毕业设计SSM抗新冠肺炎药品进销存管理系统(程序+LW)

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybatis Maven Vue 等等组成&#xff0c;B/S模式 M…

[附源码]计算机毕业设计疫情防控管理系统Springboot程序

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybatis Maven Vue 等等组成&#xff0c;B/S模式 M…

Mysql - 读写分离与读负载均衡之Maxscale

原文地址 Mysql - 读写分离与读负载均衡之Maxscale - 小豹子加油 - 博客园 maxscale会自动识别出集群的master与slave角色。所以我们可以将maxscale与mha结合起来&#xff0c;既能实现主库的故障转移&#xff0c;又能实现读写分离和从库的负载均衡。 一、概述 常见的高可用…

干货 | Burpsuite的使用tips总结

渗透测试用到Burp时候很多&#xff0c;整理了一些tips供测试时候更得心应手~ tips1:光标错位和中文显示 新版一打开容易光标错位&#xff0c;默认情况下使用字体是Courier New&#xff0c;显示不了中文。 换用Monospaced字体即可正常显示中文&#xff0c;一般这里就不会错位了…

[附源码]Python计算机毕业设计Django基于Web的绿色环保网站

项目运行 环境配置&#xff1a; Pychram社区版 python3.7.7 Mysql5.7 HBuilderXlist pipNavicat11Djangonodejs。 项目技术&#xff1a; django python Vue 等等组成&#xff0c;B/S模式 pychram管理等等。 环境需要 1.运行环境&#xff1a;最好是python3.7.7&#xff0c;…

【Matplotlib绘制图像大全】(十一):Matplotlib使用rcParams修改默认参数配置

前言 大家好,我是阿光。 本专栏整理了《Matplotlib绘制图像大全》,内包含了各种常见的绘图方法,以及Matplotlib各种内置函数的使用方法,帮助我们快速便捷的绘制出数据图像。 正在更新中~ ✨ 🚨 我的项目环境: 平台:Windows10语言环境:python3.7编译器:PyCharmMatp…

Allegro如何在PCB上开槽的三种方法操作指导

Allegro如何在PCB上开槽的三种方法操作指导 当PCB有特殊设计要求的时候,需要在PCB上开槽,Allegro支持在PCB上开槽操作,具体操作如下 以下图为例,需要在这个板框中间开槽 开方形槽 选择shape add rect命令 画在Board Geometry-outline层,type选择Unfilled 在需要开槽的…