java数据结构之图的创建与遍历

图可以使用两种存储结构,分别是邻接矩阵和邻接表。

邻接矩阵以矩阵的形式存储图所有顶点间的关系。邻接矩阵具有以下特点:

1,邻接矩阵是正矩阵,即横纵维数相等。
3,如果是无权图,则1代表有边,0代表无边,矩阵的对角线都是0;如果是有权图,矩阵点数值可以是权值
4,如果是无向图,那么矩阵是对称矩阵;如果是有向图则不一定。
4,邻接矩阵表示图的关系非常清晰,但消耗空间较大。

邻接表是以一组链表来表示顶点间关系,有以下特点:

1,邻接表示一个由链表组成的数组
2,图中的每一个顶点都有一个链,数组的大小等于图中顶点的个数。
3,无向图的链的第一个元素是本顶点,后继分别连接着和这个顶点相连的顶点;有向图的链第一个顶点是本顶点,后继是以本顶点为起点的边的终点。
4,如果是有权图,可以在节点元素中设置权值属性

5,邻接链表关系表示不如邻接矩阵清晰,数据结构相对复杂,但节省空间。

二.邻接矩阵

假设有图形结构如下:


1.建树

    /**
     * 这个类代表一条边,有起始节点和终止节点,以及边的长度
     */
    public class Edge {
        String beginCity;
        String endCity;
        int cost;
     
        public Edge(String beginCity, String endCity, int cost) {
            super();
            this.beginCity = beginCity;
            this.endCity = endCity;
            this.cost = cost;
        }
    }

    public class Graph {
        int size;
        int[][] matrix;
        String[] cityArray;
     
        /**
         *
         * @param cityArray
         *            代表所有的城市信息
         * @param edges
         *            代表所有的边
         * @param direction
         *            true代表构建有向图,false代表无向图
         */
        public Graph(String[] cityArray, Edge[] edges, boolean direction) {
            this.cityArray = cityArray;
     
            this.size = cityArray.length;
     
            matrix = new int[size][size];
            for(int i=0;i<size;i++) {
                for(int j=0;j<size;j++) {
                    
                    if(i==j) {
                        matrix[i][j] = 0;
                    }else {
                        matrix[i][j] = Integer.MAX_VALUE;
                    }
                }
            }
     
            for (Edge e : edges) {
                int begin = findIndex(e.beginCity, cityArray);
                int end = findIndex(e.endCity, cityArray);
                matrix[begin][end] = e.cost;
                if (!direction) {
                    matrix[end][begin] = e.cost;
                }
            }
     
        }
     
        /**
         * 找出指定数组中某个元素的位置,如果找不到,则返回-1
         */
        public int findIndex(String city, String[] cityArray) {
            for (int i = 0; i < cityArray.length; i++) {
                if (city.equals(cityArray[i]))
                    return i;
            }
            return -1;
        }
     
        public void print() {
     
            for (int i = 0; i < matrix.length; i++) {
                int[] ii = matrix[i];
                System.out.print(cityArray[i] + "  ");
                for (int j : ii) {
                    System.out.printf("%-16d", j);
                }
                System.out.println();
            }
        }
    }

测试代码如下:

    public class Test {
     
        public static void main(String[] args) {
     
            Graph graph = createGraph(false);
            System.out.println("图的矩阵如下:");
            graph.print();
        }
     
        /**
         *
         * @param direction
         *            是否生成有向图
         * @return
         */
        public static Graph createGraph(boolean direction) {
            String[] citys = new String[] { "北京", "上海", "广州", "重庆", "武汉", "南昌" };
     
            List<Edge> edgeList = new ArrayList<>();
            edgeList.add(new Edge("北京", "广州", 10));
            edgeList.add(new Edge("北京", "上海", 11));
            edgeList.add(new Edge("上海", "南昌", 6));
            edgeList.add(new Edge("广州", "重庆", 14));
            edgeList.add(new Edge("广州", "武汉", 9));
            edgeList.add(new Edge("重庆", "武汉", 20));
            edgeList.add(new Edge("武汉", "北京", 13));
            edgeList.add(new Edge("武汉", "南昌", 12));
            edgeList.add(new Edge("南昌", "广州", 18));
     
            Edge[] edgeArray = new Edge[edgeList.size()];
     
            return new Graph(citys, edgeList.toArray(edgeArray), true);
        }
     
    }

执行结果:

    图的矩阵如下:
    北京  0               11              10              2147483647      2147483647      2147483647      
    上海  2147483647      0               2147483647      2147483647      2147483647      6               
    广州  2147483647      2147483647      0               14              9               2147483647      
    重庆  2147483647      2147483647      2147483647      0               20              2147483647      
    武汉  13              2147483647      2147483647      2147483647      0               12              
    南昌  2147483647      2147483647      18              2147483647      2147483647      0   

注意:

我们这里表示节点时,只是用了一个String代表一个节点,如果是复杂的节点,比如

    public class City{
        String name;  //城市名字
        double gdp;  //城市GDP
        ......
    }

我们只需要将 Edge类改为

    public class Edge {
        City beginCity;
        City endCity;
        int cost;
            ......
    }

同时将Graph类中的 String[] cityArray 修改为 City[] cityArray,还有其他的修改都类似
2.深度优先级遍历

在类Graph中添加dfs()方法

    public class Graph {
        int size;
        int[][] matrix;
        String[] cityArray;
     
        .......
     
        /**
         * 对图执行深度优先级遍历
         *
         * @param start
         *            遍历其实节点
         * @param visit
         *            保存所有节点的遍历状态
         */
        public void dfs(int start, int[] visit) {
            // 访问当前节点
            System.out.print(cityArray[start] + "  ");
            visit[start] = 1;
            for (int i = 0; i < visit.length; i++) {
                if (matrix[start][i] > 0 && visit[i] == 0) {
                    dfs(i, visit);
                }
            }
        }
     
    }

测试如下:

    public class Test {
     
        public static void main(String[] args) {
     
            Graph graph = createGraph(false);

                 System.out.println("深度优先遍历顺序如下:");
             int[] visit = new int[graph.size];
                 graph.dfs(0, visit);

        }

运行结果如下:

    深度优先遍历顺序如下:
    北京  上海  广州  重庆  武汉  南昌  

3.广度优先级遍历

在类Graph中添加bfs()方法

    public class Graph {
        int size;
        int[][] matrix;
        String[] cityArray;
     
        .......
     
        /**
         * 对图执行广度优先级遍历
         *
         * @param start
         *            遍历开始节点
         */
        public void bfs(int start) {
            int visit[] = new int[size];
            int[] queue = new int[size];
            int front = -1;
            int tail = -1;
     
            // 根节点入队
            tail = (tail + 1) % queue.length;
            queue[tail] = start;
            visit[start] = 1;
     
            while (front != tail) {
                // 根节点出队
                front = (front + 1) % queue.length;
                int index = queue[front];
     
                // 访问出队节点
                System.out.print(cityArray[index] + "  ");
     
                for (int i = 0; i < cityArray.length; i++) {
                    if (matrix[index][i] != 0 && visit[i] == 0) {
                        // 下一个节点入队
                        tail = (tail + 1) % queue.length;
                        queue[tail] = i;
                        visit[i] = 1;
                    }
                }
            }
        }
    }

测试如下:

    public class Test {
        public static void main(String[] args) {
            Graph graph = createGraph(false);
     
            System.out.println("广度优先遍历顺序如下:");
            graph.bfs(0);
        }
    }

运行结果如下:

    广度优先遍历顺序如下:
    北京  上海  广州  重庆  武汉  南昌  

注意:

        在进行广度优先级遍历时,这个有个坑,一开始我是在节点离开队列时,给此节点打上访问标志(visit[i] = 1),这样做有什么问题呢?

    在进行广度优先级遍历时,重庆和广州的遍历顺序在武汉的前面,重庆和武汉都指向武汉,当遍历广州节点时,执行如下代码:

                        for (int i = 0; i < cityArray.length; i++) {
                    if (matrix[index][i] != 0 && visit[i] == 0) {
                        // 下一个节点入队
                        tail = (tail + 1) % queue.length;
                        queue[tail] = i;
                        visit[i] = 1;
                    }
                }

武汉的visit状态为0,故武汉入队列,但武汉的visit状态没变

当遍历重庆节点时,武汉的visit == 0,故武汉节点又进队里,这样就重复访问了武汉

所以:不能在节点出队列时才修改Visit状态,应该在节点入队列时设置 visit[i] =1
4.Prim算法--最小生成树

    public class Test {
     
        public static void main(String[] args) {
     
            Graph graph = createGraph(false);
             int sum = prim(graph, 0);
             System.out.println("最小路径总长度是:" + sum);
        }
     
        /**
         *
         * @param direction
         *            是否生成有向图
         * @return
         */
        public static Graph createGraph(boolean direction) {
            String[] citys = new String[] { "北京", "上海", "广州", "重庆", "武汉", "南昌" };
     
            List<Edge> edgeList = new ArrayList<>();
            edgeList.add(new Edge("北京", "广州", 10));
            edgeList.add(new Edge("北京", "上海", 11));
            edgeList.add(new Edge("上海", "南昌", 6));
            edgeList.add(new Edge("广州", "重庆", 14));
            edgeList.add(new Edge("广州", "武汉", 9));
            edgeList.add(new Edge("重庆", "武汉", 20));
            edgeList.add(new Edge("武汉", "北京", 13));
            edgeList.add(new Edge("武汉", "南昌", 12));
            edgeList.add(new Edge("南昌", "广州", 18));
     
            Edge[] edgeArray = new Edge[edgeList.size()];
     
            return new Graph(citys, edgeList.toArray(edgeArray), true);
     
        }
     
        public static int prim(Graph graph, int start) {
            if (graph != null) {
                int size = graph.size;
                int[] lowCost = new int[size];
                int[] visit = new int[size];
     
                // 初始化lowCost数组
                for (int i = 0; i < size; i++) {
                    lowCost[i] = graph.matrix[start][i];
                }
     
                // 对进树节点的操作
                StringBuilder builder = new StringBuilder();
                builder.append(graph.cityArray[start]).append(" ");
     
                visit[start] = 1;
     
                int sum = 0;
     
                // 起始节点不需要找,所以我们总共要找(size-1)个节点,故这里n从1开始
                for (int n = 1; n < size; n++) {
     
                    int min = Integer.MAX_VALUE;
                    int k = -1;
     
                    // 选出下一个进树的节点
                    for (int i = 0; i < size; i++) {
                        if (visit[i] == 0 && lowCost[i] < min) {
                            min = lowCost[i];
                            k = i;
                        }
                    }
     
                    builder.append(graph.cityArray[k]).append(" ");
                    visit[k] = 1;
                    sum += min;
     
                    // 更新剩下节点的lowCost
                    for (int i = 0; i < size; i++) {
                        if (visit[i] == 0 && graph.matrix[k][i] < lowCost[i]) {
                            lowCost[i] = graph.matrix[k][i];
                        }
                    }
     
                }
     
                System.out.println("Prim 数的构造顺序如下:");
                System.out.println(builder.toString());
                return sum;
            }
     
            return 0;
        }
     
    }

运行结果如下:

    Prim 数的构造顺序如下:
    北京 广州 武汉 上海 南昌 重庆
    最小路径总长度是:50

5.Dijkstra--最短路径算法

    public class Test {
     
        public static void main(String[] args) {
     
            Graph graph = createGraph(false);
     
            int[] dist = new int[graph.size];
            int[] path = new int[graph.size];
            djst(graph, 0, dist, path);
            
            for(int i=0;i<graph.size;i++) {
                printPath(path, i);
                System.out.println();
            }
        }
     
        /**
         *
         * @param direction
         *            是否生成有向图
         * @return
         */
        public static Graph createGraph(boolean direction) {
            String[] citys = new String[] { "北京", "上海", "广州", "重庆", "武汉", "南昌" };
     
            List<Edge> edgeList = new ArrayList<>();
            edgeList.add(new Edge("北京", "广州", 10));
            edgeList.add(new Edge("北京", "上海", 11));
            edgeList.add(new Edge("上海", "南昌", 6));
            edgeList.add(new Edge("广州", "重庆", 14));
            edgeList.add(new Edge("广州", "武汉", 9));
            edgeList.add(new Edge("重庆", "武汉", 20));
            edgeList.add(new Edge("武汉", "北京", 13));
            edgeList.add(new Edge("武汉", "南昌", 12));
            edgeList.add(new Edge("南昌", "广州", 18));
     
            Edge[] edgeArray = new Edge[edgeList.size()];
     
            return new Graph(citys, edgeList.toArray(edgeArray), true);
     
        }
     
     
        /**
         *
         * @param graph
         * @param start
         * @param dist
         *            各个节点到起始节点start的最短距离
         * @param path
         *            各个节点到起始节点start的路径的前一个节点
         */
        public static void djst(Graph graph, int start, int[] dist, int[] path) {
            int size = graph.size;
            int[][] matrix = graph.matrix;
            int[] visit = new int[size];
     
            // 初始化数组path和dist
            for (int i = 0; i < size; i++) {
                dist[i] = matrix[start][i];
     
                if (matrix[start][i] < Graph.MAX) {
                    path[i] = start;
                } else {
                    path[i] = -1;
                }
            }
     
            // 前面的for循环将起始节点的path值设为了start
            // 这里将起始节点的前一个节点为-1
            // 整个Djst算法结束后,只有起始节点的path值为-1
            path[start] = -1;
     
            visit[start] = 1;
     
            // 起始节点不需要找,所以我们总共要找(size-1)个节点,故这里n从1开始
            for (int n = 1; n < size; n++) {
                int min = Graph.MAX;
                int k = -1;
     
                // 找出下一个节点
                for (int i = 0; i < size; i++) {
                    if (visit[i] == 0 && dist[i] < min) {
                        k = i;
                        min = dist[i];
                    }
                }
     
                visit[k] = 1;
     
                // 找到了最短的节点之后,更新剩下节点的dist距离
                for (int i = 0; i < size; i++) {
                    if ((visit[i] == 0) && (dist[k] + matrix[k][i] < dist[i])) {
                        dist[i] = dist[k] + matrix[k][i];
                        path[i] = k;
                    }
                }
            }
     
        }
     
        /**
         * 打印出起始结点到每个节点的路径 path[]数组中保存了一棵双亲存储结构的树,默认输出的是叶子节点到根节点路径,
         *
         * 我们使用了一个栈,从而实现了逆向输出。
         *
         */
        public static void printPath(int[] path, int target) {
     
            //
            int[] stack = new int[path.length];
            int pos = -1;
     
            int index = target;
     
            while (index != -1) {
                stack[++pos] = index;
                index = path[index];
            }
     
            while (pos!=-1) {
                System.out.print(stack[pos--] + "  ");
            }
        }
     
    }

结果如下:

    0  
    0  1  
    0  2  
    0  2  3  
    0  2  4  
    0  1  5  

注意:Graph.MAX 代表两节点没直接相连时的无穷大距离,一开始我设置的数值是 Graph.MAX  = Integer.Max_Value,但是有一个错误,当执行到

    // 找到了最短的节点之后,更新剩下节点的dist距离
                for (int i = 0; i < size; i++) {
                    if ((visit[i] == 0) && (dist[k] + matrix[k][i] < dist[i])) {
                        dist[i] = dist[k] + matrix[k][i];
                        path[i] = k;
                    }
                }

matrix[k][i]的值可能是 Integer.Max_Value,当和dist[k]相加时,得到的结果超过了Int的表示范围,

从而结果是是负数,当然结果就会有错误

故正确设置 Graph.MAX = Integer.Max_Value >>1
6.佛洛依德Floyd --最短路径算法

    public class Test {
     
        public static void main(String[] args) {
     
            Graph graph = createGraph(false);
     
            int[][] dist2 = new int[graph.size][graph.size];
            int[][] path2 = new int[graph.size][graph.size];
     
            floyd(graph, dist2, path2);
     
            for (int i = 0; i < graph.size; i++) {
                System.out.println(dist2[0][i]);
            }
     
            printFloydPath(path2, 0, 4);
     
        }
     
        public static void floyd(Graph graph, int[][] dist, int[][] path) {
            if (graph != null) {
                int size = graph.size;
                // 初始化矩阵 path和dist
                for (int i = 0; i < size; i++) {
                    for (int j = 0; j < size; j++) {
                        path[i][j] = -1;
                        dist[i][j] = graph.matrix[i][j];
                    }
                }
     
                /**
                 * 下面这个三层循环是本运算的主要操作,完成了以K为中间节点对所有(i,j)进行检测和修改
                 */
                for (int k = 0; k < size; k++)
                    for (int i = 0; i < size; i++)
                        for (int j = 0; j < size; j++) {
                            if (dist[i][k] + dist[k][j] < dist[i][j]) {
                                dist[i][j] = dist[i][k] + dist[k][j];
                                path[i][j] = k;
                            }
                        }
     
            }
        }
     
        /**
         * 打印两个节点之前的中间节点
         *
         * @param path
         * @param start
         * @param end
         */
        public static void printFloydPath(int[][] path, int start, int end) {
            if (path[start][end] != -1) {
                int mid = path[start][end];
                System.out.println(mid);
                printFloydPath(path, start, mid);
                printFloydPath(path, mid, end);
            }
        }
     
    }

结果如下:

    0
    11
    10
    24
    19
    17
    节点0到节点4之间的节点有:
    2


二.邻接表

假设有图形结构如下:


源码如下:

    /**
     * 这个类代表一条边,有起始节点和终止节点,以及边的长度
     *
     * 这是从用户角度来看图,用户只知道这种形式的边,不涉及任何指向下一条边的指针
     */
    public class Edge {
        String beginCity;
        String endCity;
        int cost;
     
        public Edge(String beginCity, String endCity, int cost) {
            super();
            this.beginCity = beginCity;
            this.endCity = endCity;
            this.cost = cost;
        }
    }

    /**
     *  Arc弧线,代表着下一层的小弟
     *      target 下一层的小弟是谁
     *      next   指向下一层的指针
     *      cost   和下一层的等级差距
     *  
     */
    public class Arc {
        String target;
        Arc next;
        int cost;
     
        public Arc(String target, Arc next, int cost) {
            super();
            this.target = target;
            this.next = next;
            this.cost = cost;
        }
     
    }

    /**
     * 类Node(value,firstAct) 代表着某个部门, value是这位部门老大的信息,firstAct代表着这个部门老大下面的小弟,
     *
     * 类Act代表着一个小弟,类Act中的target 和类Node的value是相同性质的数据,
     * 只不过value代表老大的个人信息,target代表小弟的个人信息
     *
     * 老大和小弟唯一不同的是:老大没有 cost属性,因为老大是食物链顶端
     *
     * 一个层的小弟在另一个层中就是老大,所以一个小弟的target 肯定会等于某个老大的target
     *
     */
    public class Node {
        String value;
        Arc firstAct;
     
        public Node(String value, Arc firstAct) {
            super();
            this.value = value;
            this.firstAct = firstAct;
        }
     
        public void addNode(String target, int cost) {
            if (target != null) {
     
                if (firstAct == null) {
                    firstAct = new Arc(target, null, cost);
                } else {
                    // 找到此链表的最后一个节点
                    Arc arc = firstAct;
                    while (arc.next != null) {
                        arc = arc.next;
                    }
     
                    arc.next = new Arc(target, null, cost);
     
                }
     
            }
        }
     
        public static final String TAG_ARROW = "->";
     
        @Override
        public String toString() {
     
            // 无论怎样,就算这个部门没有小弟,至少还有个光杆司令
            StringBuilder builder = new StringBuilder();
            builder.append(value).append(TAG_ARROW);
     
            if (firstAct != null) {
                Arc arc = firstAct;
                while (arc != null) {
                    builder.append(arc.target).append(TAG_ARROW);
                    arc = arc.next;
                }
            }
     
            String str = builder.toString();
            return str.substring(0, str.lastIndexOf(TAG_ARROW));
        }
    }

    /**
     * 邻接表表示图型结构可以这样理解: Graph代表一个公司老总,Node就代表一个部门, 老总直接管理着很多个部门
     *
     * 类Node(value,firstAct) 代表着某个部门, value是这位部门老大的信息,firstAct代表着这个部门老大下面的小弟,
     *
     * 类Act代表着一个小弟,类Act中的target 和类Node的value是相同性质的数据,
     * 只不过value代表老大的个人信息,target代表小弟的个人信息
     *
     * 老大和小弟唯一不同的是:老大没有 cost属性,因为老大是食物链顶端
     *
     */
    public class Graph {
        String[] cityArray;
        Node[] nodeArray;
        int size;
     
        /**
         * 这是完全模拟从用户角度来建图的过程,用户只知道提供所有城市和城市之间的简单连线, 而我们要做的事,就是将用户提供的这种简单数据变成具有逻辑的结构
         *
         */
        public Graph(String[] cityArray, Edge[] edges, boolean direction) {
     
            if (cityArray != null && cityArray.length > 0 && edges != null && edges.length > 0) {
                this.cityArray = cityArray;
                this.size = cityArray.length;
     
                nodeArray = new Node[size];
                for (int i = 0; i < nodeArray.length; i++) {
                    nodeArray[i] = new Node(cityArray[i], null);
                }
     
                for (Edge e : edges) {
                    int index = findIndex(e.beginCity, cityArray);
                    nodeArray[index].addNode(e.endCity, e.cost);
                    if (!direction) {
                        index = findIndex(e.endCity, cityArray);
                        nodeArray[index].addNode(e.beginCity, e.cost);
                    }
                }
                /**
                 * 注意1
                 */
            }
     
        }
     
        /**
         * 找出指定数组中某个元素的位置,如果找不到,则返回-1
         */
        public int findIndex(String city, String[] cityArray) {
            for (int i = 0; i < cityArray.length; i++) {
                if (city.equals(cityArray[i]))
                    return i;
            }
            return -1;
        }
     
        public void print() {
     
            for (Node node : nodeArray)
                System.out.println(node);
        }
     
        public void dfs(int start, int[] visit) {
            Node node = nodeArray[start];
            visit[start] = 1;
            // 接下來是访问节点操作
            System.out.print(node.value + " ");
     
            Arc arc = node.firstAct;
            while (arc != null) {
                int index = findIndex(arc.target, cityArray);
                if (visit[index] == 0)
                    dfs(index, visit);
     
                arc = arc.next;
            }
     
        }
     
        /**
         * 对图执行广度优先级遍历
         *
         * @param start
         *            遍历开始节点
         */
        public void bfs(int start) {
            int visit[] = new int[size];
     
            int[] queue = new int[size];
            int front = -1;
            int tail = -1;
     
            // 根节点入队
            tail = (tail + 1) % queue.length;
            queue[tail] = start;
            visit[start] = 1;
     
            while (front != tail) {
                // 根节点出队
                front = (front + 1) % queue.length;
                int index = queue[front];
     
                // 访问出队节点
                System.out.print(cityArray[index] + "  ");
     
                Arc arc = nodeArray[index].firstAct;
                while (arc != null) {
                    int ii = findIndex(arc.target, cityArray);
                    if (visit[ii] == 0) {
                        // 下一个节点入队
                        tail = (tail + 1) % queue.length;
                        queue[tail] = ii;
                        visit[ii] = 1;
                    }
     
                    arc = arc.next;
                }
     
            }
        }
     
    }


测试代码如下:

    public class Test {
     
        public static void main(String[] args) {
     
            Graph graph = createGraph(true); //建立有向图
            System.out.println("图的矩阵如下:");
            graph.print();
     
            System.out.println("深度优先遍历顺序如下:");
            int[] visit = new int[graph.size];
            graph.dfs(0, visit);
     
             System.out.println("");
             System.out.println("广度优先遍历顺序如下:");
             graph.bfs(0);
     
        }
     
        public static Graph createGraph(boolean direction) {
            String[] citys = new String[] { "北京", "上海", "广州", "重庆", "武汉", "南昌" };
     
            List<Edge> edgeList = new ArrayList<>();
            edgeList.add(new Edge("北京", "上海", 11));
            edgeList.add(new Edge("北京", "广州", 10));
            edgeList.add(new Edge("上海", "南昌", 6));
            edgeList.add(new Edge("广州", "重庆", 14));
            edgeList.add(new Edge("广州", "武汉", 9));
            edgeList.add(new Edge("重庆", "武汉", 20));
            edgeList.add(new Edge("武汉", "北京", 13));
            edgeList.add(new Edge("武汉", "南昌", 12));
            edgeList.add(new Edge("南昌", "广州", 18));
     
            Edge[] edgeArray = new Edge[edgeList.size()];
     
            return new Graph(citys, edgeList.toArray(edgeArray), direction);
     
        }
     
    }

 

原文:https://blog.csdn.net/weixin_38287116/article/details/79498100版权声明:本文为博主原创文章,转载请附上博文链接!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值