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

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

要用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. 邻接矩阵存储图,并实现基本操作

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

每一行代表一个顶点,依次从 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:
在这里插入图片描述

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

这份代码处理了有向图和无向图的情况,需要记住十字链表法一般储存有向图.

具体的代码处理:如果是无向图,重新递归调用AddEdge函数只不过原节点和目的节点参数调换即可。

#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;
}

在这里插入图片描述

4. 邻接多重表(储存无向图)

图片来源

邻接多重表是一种专门存储无向图(网)的结构。

邻接多重表存储无向图的方式,可以看作是邻接表和十字链表的结合体。

具体来讲就是:将图中的所有顶点存储到顺序表(也可以用链表)中,同时为每个顶点配备一个链表,链表的各个结点中存储的都是和当前顶点有直接关联的边。
在这里插入图片描述

在邻接多重表中,每一条边是同过节点来表示的。
这个节点有6个成员
在这里插入图片描述

1. mark:标记域,标记这条边是否被搜索过,例如遍历无向图中的所有边,借助 mark 标志域可以避免重复访问同一条边;
2. ivex,jvex:为该边依附的两个顶点在循序表的位置
3. ilink:指向下一条依附于ivex的边
4. jink:指向下一条依附于jvex的边
5. info:存储当前边的其它信息,可以用 info 指针域存储边的权值。

无向图中的每一个节点中包含两部分1. 这个节点的值 2. 保存边关系的结构体

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

typedef char ValueType;

struct Data
{
    bool mark = false;     // 标记域,标记这条边是否被搜索过
    size_t ivex = -1;      // 为该边依附的两个顶点在循序表的位置
    Data *ilink = nullptr; // 指向下一条依附于ivex的边
    size_t jvex = -1;
    Data *jlink = nullptr;
    int info = INT_MAX; // 权值,默认为无穷
};

struct MatrixNode
{
    ValueType value = ValueType(); // 初始化节点类型int和char型为0,其他类型调用其默认构造函数
    Data *list = nullptr;
};

class AdjacencyMultilist
{
private:
    std::vector<MatrixNode> _matrix;
    // 图节点和在_matrix下标的映射
    std::unordered_map<ValueType, size_t> _hashMap;

    size_t _getValeTypePos(const ValueType &dst)
    {
        auto pos = _hashMap.find(dst);
        if (pos == _hashMap.end())
        {
            std::cout << "dst point not found" << std::endl;
            assert(pos != _hashMap.end());
        }
        return pos->second;
    }

public:
    AdjacencyMultilist(const std::vector<ValueType> &vet)
    {
        _matrix.resize(vet.size()); // 先开辟内存
        for (int i = 0; i < vet.size(); i++)
        {
            _matrix[i].value = vet[i];
            _hashMap[vet[i]] = i;
        }
    }

    /**
     * @brief 向无向图中添加边关系
     *
     * @param src 边起点
     * @param dst 边终点
     * @param info 边权值
     */
    void AddEdge(const ValueType &src, const ValueType &dst, int info)
    {
        // 找到这两个点在_matrix顺序表的位置
        size_t posSrc = _getValeTypePos(src);
        size_t posDst = _getValeTypePos(dst);

        // 创建边节点
        Data *edge = new Data;
        edge->ivex = posSrc;
        edge->jvex = posDst;
        edge->info = info;

        // 头插法插入
        edge->ilink = _matrix[posSrc].list;
        _matrix[posSrc].list = edge;

        edge->jlink = _matrix[posDst].list;
        _matrix[posDst].list = edge;
    }

    // /*
    //  * 删除(src,dst) 或者(dst,src)
    //  * 实现思路:
    //  * 1、从 src 顶点的链表出发,找到目标结点的直接前驱结点;
    //  * 2、从 dst 顶点的链表触发,找到目标结点的直接前驱结点;
    //  * 3、将目标结点从 src 顶点的链表中摘除,从 dst 顶点的链表中摘除
    //  * 4、删除目标结点
    //  */

    // void DelEdge(const ValueType &src, const ValueType &dst)
    // {
    //     size_t posSrc = _getValeTypePos(src);
    //     size_t posDst = _getValeTypePos(dst);
    //     Data *srcPrev = nullptr;
    //     Data *dstPrev = nullptr;
    //     Data *node = _matrix[posSrc].list;
    //     // 找src点的直接前驱
    //     while (node != nullptr && ((node->ivex == posSrc && node->jvex == posDst) || (node->ivex == posDst && node->jvex == posSrc)))
    //     {
    //         // 因为邻接多重表只能保存有向图,删除边,这两个方向都需要删除
    //         srcPrev = node;
    //         if (node->ivex == posSrc)
    //         {
    //             node = node->ilink;
    //         }
    //         else
    //         {
    //             node = node->jlink;
    //         }
    //     }
    //     // 找dst点前驱
    //     Data *jnode = _matrix[posDst].list;
    //     while (jnode != nullptr && ((jnode->ivex == posSrc && jnode->jvex == posDst) || (jnode->ivex == posDst && jnode->jvex == posSrc)))
    //     {
    //         dstPrev = jnode;
    //         if (jnode->ivex == posDst)
    //         {
    //             jnode = jnode->ilink;
    //         }
    //         else
    //         {
    //             jnode = jnode->jlink;
    //         }
    //     }

    //     if (node == nullptr || jnode == nullptr)
    //     {
    //         std::cout << "edge not exit" << std::endl;
    //         return;
    //     }

    //     // 将目标结点从 src 顶点的链表中摘除,从 dst 顶点的链表中摘除
    //     if (srcPrev == nullptr)
    //     {
    //         // 头删
    //         if (_matrix[posSrc].list->ivex == posSrc)
    //         {
    //             _matrix[posSrc].list = node->ilink;
    //         }
    //         else
    //         {
    //             _matrix[posSrc].list = node->ilink;
    //         }
    //     }
    //     else
    //     {
    //         if (srcPrev->ivex == posSrc)
    //         {
    //             if (node->ivex == posSrc)
    //             {
    //                 srcPrev->ilink = node->ilink;
    //             }
    //             else
    //             {
    //                 srcPrev->ilink = node->jlink;
    //             }
    //         }
    //         else
    //         {
    //             if (node->ivex == posSrc)
    //             {
    //                 srcPrev->jlink = node->ilink;
    //             }
    //             else
    //             {
    //                 srcPrev->jlink = node->jlink;
    //             }
    //         }
    //     }

    //     // 从 dst 顶点的链表中摘除
    //     if (dstPrev == nullptr)
    //     {
    //         // 头删
    //         if (_matrix[posDst].list->ivex == posDst)
    //         {
    //             _matrix[posDst].list = node->ilink;
    //         }
    //         else
    //         {
    //             _matrix[posDst].list = node->ilink;
    //         }
    //     }
    //     else
    //     {
    //         if (dstPrev->ivex == posDst)
    //         {
    //             if (jnode->ivex == posDst)
    //             {
    //                 dstPrev->ilink = node->ilink;
    //             }
    //             else
    //             {
    //                 dstPrev->ilink = node->jlink;
    //             }
    //         }
    //         else
    //         {
    //             if (jnode->ivex == posDst)
    //             {
    //                 dstPrev->jlink = jnode->ilink;
    //             }
    //             else
    //             {
    //                 dstPrev->jlink = jnode->jlink;
    //             }
    //         }
    //     }

    //     // 删除边
    //     delete node;
    //     _matrix[posSrc].list = nullptr;
    // }

    // 输出邻接多重表中包含的所有边
    void PrintGraph()
    {
        for (int i = 0; i < _matrix.size(); i++)
        {
            // 如果当前结点存在,且标志域为 0
            Data *p = _matrix[i].list;
            while (p && (p->mark == false))
            {
                // 输出该边,并将标志域置为 1
                printf("%c-%c 权值%d ", _matrix[p->ivex].value, _matrix[p->jvex].value, p->info);
                p->mark = true;
                if (p->ivex == i)
                {
                    p = p->ilink;
                }
                else
                {
                    p = p->jlink;
                }
            }
        }
        printf("\n");
    }
};
#include "./AdjacencyMultilist.h"

int main(int argc, char const *argv[])
{
    AdjacencyMultilist graph({'a', 'b', 'c', 'd'});

    graph.AddEdge('a', 'b', 10);
    graph.AddEdge('c', 'b', 1);
    graph.AddEdge('c', 'd', 5);
    graph.PrintGraph();

    // graph.DelEdge('c', 'b');
    // graph.PrintGraph();
    return 0;
}

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

NUC_Dodamce

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值