C++数据结构之图的最短路径——Dijkstra&Floyd(包含算法实现以及gif图示)

目录

一、介绍

概念

最短路径与最小生成树的不同点

二、Dijkstra算法

算法介绍

算法思想与算法举例

算法实现

代码执行结果

三、Floyd算法

算法介绍

算法思想

算法举例

算法实现

代码执行结果

三、总结


一、介绍

概念

路径:

从图或者网中的某个顶点,到另一个顶点所经过的点和边的集合,可以用多种储存方式来储存

最短路径:

从图中一个起点到其余各个顶点的最短路径

路径长度:

一条路径上所经过的边的数目

带权路径长度:

路径上所经过的边的权值之和

最短路径长度(最短距离):

最短路径的(带权值)路径长度

tips:因为不带权的情况比较简单,我们就带权值的连通网来进行举例

示例图

如该图所示:我们设起点为顶点0,那么到达各个顶点就有多条路径,到达顶点1的有:0-1,到达顶点2的有:0-1-2、0-3-2,到达顶点3的有:0-3,到达顶点4的有:0-4、0-1-2-4,那么该图的最短路径就是0-1、0-3-2、0-4(怎么求出来的后面会解释),0-1这条路径的路径长度为1,带权路径长度为5

最短路径与最小生成树的不同点

1.最短路径的操作对象是有向图或者有向网,而最小生成树的操作对象是无向图

2.最短路径有一个起点,但是最小生成树没有

3.最短路径所关注的点是起点到其余各点的路径最短,然而最小生成树所关注的点是整棵树的代价最小

二、Dijkstra算法

算法介绍

狄克斯特拉算法是一种使用了回溯思想的算法,并使用辅助数组用来搜寻最短路径,每次迭代都可以得到一个最短路径,因此是一个非常稳定的算法

算法思想与算法举例

设我们有下图这么一个连通网

1.首先设置一个二维辅助数组distance[i][j](其中i为当前顶点,j表示从顶点i到顶点j的权值,若顶点i与顶点j不相连,则距离为无穷大,且顶点i到顶点i的距离为0)

2.选择一个起点,由该顶点进行dijkstra算法,我们选择顶点0

3.我们观察顶点0的出弧(也就是顶点0能到达的顶点),发现0能直接到达的顶点有顶点3和顶点2,那么我们就可以得到distance[0][]如下图所示

由此我们可以看到除去与本身的距离,<0,2>这条弧权值是最小的,那么我们就可以直接确定顶点0到顶点2的最短路径就是0-2

4.然后我们从顶点2出发,一样得到顶点2的distance[2][j]如下图所示

 除去与本身的距离我们可以发现<2,5>这条弧上的权值是最小的,注意:此时我们并不是直接就把0-2-5这条路径作为顶点0到达顶点5的最短路径,而是返回到上一个顶点进行查询,也就是查询是否存在<0,5>这条弧且其权值小于从起点到顶点2的最短路径长度加上<2,5>这条弧的权值的路径(简单的来说就是回退到顶点0看一下能否有更短的可以直接从顶点0到达顶点5的路径),经过查询之后发现不存在,因此直接将0-2-5确定为顶点0到顶点5的最短路径。

 5.然后我们从顶点5出发,进行同样的操作

 得到权值最小的弧为<5,3>,此时我们回退到顶点2,查询是否存在<2,3>这条弧并判断从起点到达顶点2的最短路径长度加上<2,3>这条弧的权值是否小于0-2-5-3这条带权路径的长度,经查询之后发现不存在这条弧那么直接确定0-2-5-3这条路径就是从起点到顶点3的最短路径

当最后一个顶点无法找到任何一条出弧时,这个时候我们就要进行回溯,找到之前所记录的权值最小的边,按照这个规则我们找到了<2,1>这条弧,我们直接对顶点1进行访问,对顶点1进行访问完毕之后我们按照之前的规则,查询是否存在<2,4>这条弧并且<2,4>这条弧的权值小于2-1-4这条带权路路径的长度,经查询之后发现不存在那么我们就可以直接确定0-2-1-4这条路径就是从起点到顶点4的最短路径

 当全部顶点被访问完毕,此算法结束

算法实现

要实现这个算法,我们需要创建几个数组用来辅助

distance[i]:用来表示起点到顶点i的最短路径的距离

prev[i]:用来表示起点到顶点i的最短路径的前驱顶点

flag[i]:用来表示顶点i是否被访问过

1.无向图类

class undigraph
{
    public:
    //int vertexnum=0;
    int vertexnum=7;
    //图以邻接矩阵的形式存储
    //int adjmatrix[maxsize][maxsize];//如果要自己输入图就把注释掉的取消注释并将对应代码注释掉
    int adjmatrix[7][7]=
    {
        {0,5,7,infinity,infinity,infinity,2},
        {5,0,infinity,9,infinity,infinity,3},
        {7,infinity,0,infinity,8,infinity,3},
        {infinity,9,infinity,0,infinity,4,infinity},
        {infinity,infinity,8,infinity,0,5,4},
        {infinity,infinity,infinity,4,5,0,6},
        {2,3,3,infinity,4,6,0}
    };
    int start=0;
    //int start;
    int distance[maxsize];//起点与各顶点间最短距离数组
    int prev[maxsize];//起点与各顶点之间的最短路径的前驱数组
    int flag[maxsize];//顶点访问数组用来表示是否已经被访问
    //创建图
    void createundigraph();
    //初始化
    void initdistance();
    void initpre();
    void initflag();
    //打印数组
    void printdistance();//打印距离数组
    void printprev();//打印前驱数组
    void printpath();//打印最短路径
    //最短路径算法
    void dijkstra();
};

2.初始化

void undigraph::initdistance()
{
    for (int i = 0; i < vertexnum; i++)
    {
        distance[i]=adjmatrix[start][i];//初始起点到达其他所有顶点的最短路径长度就是邻接矩阵中的权值  
    } 
}

void undigraph::initpre()
{
    for (int i = 0; i < vertexnum; i++)
    {
        prev[i]=i;//初始所有的点所在的最短路径的前驱顶点都是自身
    }
    
}

void undigraph::initflag()
{
    for (int i = 0; i < vertexnum; i++)
    {
        flag[i]=0;//初始所有顶点都未被访问过
    }
    
}

3.dijkstra算法

void undigraph::dijkstra()
{
    //用户输入起点
    cout<<"请输入起点:"<<endl;
    cin>>start;
    //初始化顶点
    //1.对顶点进行访问
    flag[start]=1;
    //2.起点到起点的最短路径长度为0
    distance[start]=0;
    //定义一个变量用来储存即将访问的顶点
    int k=start;
    //因为起点已经被初始化了,此时我们还剩下vertexnum-1个顶点需要进行访问和求取最短路径
    for (int i = 0; i < vertexnum-1; i++)
    {
        //每次寻找当前顶点的最短路径的时候都要将最小权值初始化为无穷
        int min=infinity;
        //经过循环之后找出与当前点相连接的权值最小的顶点并记录其下标
        for (int j = 0; j < vertexnum; j++)
        {
            if (flag[j]==0&&distance[j]<min)
            {
                min=distance[j];
                k=j;
            }
        }
        //对其进行访问
        flag[k]=1;
        //对这个访问的顶点进行最短路径修正
        int temp;
        for (int p = 0; p < vertexnum; p++)
        {
            if (adjmatrix[k][p]==infinity)//如果最后访问的顶点k与当前所选中的顶点不连通的话
            {
                temp=infinity;//将当前最短距离设置为无穷
            }
            else
            {
                temp=min+adjmatrix[k][p];//若连通就将当前距离设置为起点到顶点k的最短距离加上(k,p)的权值
            }
            
            if (flag[p]==0&&temp<distance[p])//如果顶点p未被访问过,且temp小于顶点直接到达顶点p的距离
            {
                distance[p]=temp;//就将temp设置为起点到达顶点p的最短距离
                prev[p]=k;//并将顶点p的前驱顶点设置为顶点k
            }
        }  
    }
}

4.打印最短距离数组、前驱顶点数组、最短路径

void undigraph::printdistance()
{
    cout<<"=========distance========="<<endl;
    for (int i = 0; i < vertexnum; i++)
    {
        cout<<distance[i]<<"  ";
    }
    cout<<endl;
}

void undigraph::printprev()
{
    cout<<"=========prev========="<<endl;
    for (int i = 0; i < vertexnum; i++)
    {
        cout<<prev[i]<<"  ";
    }
    cout<<endl;
}

void undigraph::printpath()
{
    for (int i = 0; i < vertexnum; i++)
    {
        cout<<"顶点"<<start<<"到顶点"<<i<<"的最短路径为;"<<endl;
        cout<<i<<"<---";
        int tmp=prev[i];
        while (true)
        {
            if (tmp==i)
            {
                break;
            }
            if (tmp==prev[tmp])
            {
                cout<<tmp<<"<---";
                break;
            }
            cout<<tmp<<"<---";
            tmp=prev[tmp];
        }
        cout<<start<<endl;
    } 
}

5.全部代码

#include<stdio.h>
#include<iostream>
#define maxsize 100
#define infinity 999
using namespace std;

class undigraph
{
    public:
    //int vertexnum=0;
    int vertexnum=7;
    //图以邻接矩阵的形式存储
    //int adjmatrix[maxsize][maxsize];//如果要自己输入图就把注释掉的取消注释并将对应代码注释掉
    int adjmatrix[7][7]=
    {
        {0,5,7,infinity,infinity,infinity,2},
        {5,0,infinity,9,infinity,infinity,3},
        {7,infinity,0,infinity,8,infinity,3},
        {infinity,9,infinity,0,infinity,4,infinity},
        {infinity,infinity,8,infinity,0,5,4},
        {infinity,infinity,infinity,4,5,0,6},
        {2,3,3,infinity,4,6,0}
    };
    int start=0;
    //int start;
    int distance[maxsize];//起点与各顶点间最短距离数组
    int prev[maxsize];//起点与各顶点之间的最短路径的前驱数组
    int flag[maxsize];//顶点访问数组用来表示是否已经被访问
    //创建图
    void createundigraph();
    //初始化
    void initdistance();
    void initpre();
    void initflag();
    //打印数组
    void printdistance();//打印距离数组
    void printprev();//打印前驱数组
    void printpath();//打印最短路径
    //最短路径算法
    void dijkstra();
};

void undigraph::createundigraph()
{
    
    int tempdata;
    int adjvertex;
    int i=0;
    for(;;i++)
    {
        cout<<"请输入顶点"<<i<<"数据:"<<endl;
        cin>>tempdata;
        vertexnum+=1;
        if (tempdata==-1)
        {
            break;
        }
        
        cout<<"请输入该顶点的相邻顶点:"<<endl;
        cin>>adjvertex;
        
        adjmatrix[i][adjvertex]=1;
        adjmatrix[adjvertex][i]=1;
    }
    cout<<"邻接矩阵构建完毕"<<endl;
    
}

void undigraph::initdistance()
{
    for (int i = 0; i < vertexnum; i++)
    {
        distance[i]=adjmatrix[start][i];//初始起点到达其他所有顶点的最短路径长度就是邻接矩阵中的权值  
    } 
}

void undigraph::initpre()
{
    for (int i = 0; i < vertexnum; i++)
    {
        prev[i]=i;//初始所有的点所在的最短路径的前驱顶点都是自身
    }
    
}

void undigraph::initflag()
{
    for (int i = 0; i < vertexnum; i++)
    {
        flag[i]=0;//初始所有顶点都未被访问过
    }
    
}

void undigraph::dijkstra()
{
    //用户输入起点
    cout<<"请输入起点:"<<endl;
    cin>>start;
    //初始化顶点
    //1.对顶点进行访问
    flag[start]=1;
    //2.起点到起点的最短路径长度为0
    distance[start]=0;
    //定义一个变量用来储存即将访问的顶点
    int k=start;
    //因为起点已经被初始化了,此时我们还剩下vertexnum-1个顶点需要进行访问和求取最短路径
    for (int i = 0; i < vertexnum-1; i++)
    {
        //每次寻找当前顶点的最短路径的时候都要将最小权值初始化为无穷
        int min=infinity;
        //经过循环之后找出与当前点相连接的权值最小的顶点并记录其下标
        for (int j = 0; j < vertexnum; j++)
        {
            if (flag[j]==0&&distance[j]<min)
            {
                min=distance[j];
                k=j;
            }
        }
        //对其进行访问
        flag[k]=1;
        //对这个访问的顶点进行最短路径修正
        int temp;
        for (int p = 0; p < vertexnum; p++)
        {
            if (adjmatrix[k][p]==infinity)//如果最后访问的顶点k与当前所选中的顶点不连通的话
            {
                temp=infinity;//将当前最短距离设置为无穷
            }
            else
            {
                temp=min+adjmatrix[k][p];//若连通就将当前距离设置为起点到顶点k的最短距离加上(k,p)的权值
            }
            
            if (flag[p]==0&&temp<distance[p])//如果顶点p未被访问过,且temp小于顶点直接到达顶点p的距离
            {
                distance[p]=temp;//就将temp设置为起点到达顶点p的最短距离
                prev[p]=k;//并将顶点p的前驱顶点设置为顶点k
            }
 
        }
        
    }
    
}

void undigraph::printdistance()
{
    cout<<"=========distance========="<<endl;
    for (int i = 0; i < vertexnum; i++)
    {
        cout<<distance[i]<<"  ";
    }
    cout<<endl;
}

void undigraph::printprev()
{
    cout<<"=========prev========="<<endl;
    for (int i = 0; i < vertexnum; i++)
    {
        cout<<prev[i]<<"  ";
    }
    cout<<endl;
}

void undigraph::printpath()
{
    for (int i = 0; i < vertexnum; i++)
    {
        cout<<"顶点"<<start<<"到顶点"<<i<<"的最短路径为;"<<endl;
        cout<<i<<"<---";
        int tmp=prev[i];
        while (true)
        {
            if (tmp==i)
            {
                break;
            }
            if (tmp==prev[tmp])
            {
                cout<<tmp<<"<---";
                break;
            }
            cout<<tmp<<"<---";
            tmp=prev[tmp];
        }
        cout<<start<<endl;
    } 
}

int main()
{
    undigraph ug;
    ug.initdistance();
    ug.initflag();
    ug.initpre();
    ug.dijkstra();
    ug.printdistance();
    ug.printprev();
    ug.printpath();
    return 0;
}

代码执行结果

我们将下图导入到代码中得到执行结果

三、Floyd算法

算法介绍

Floyd算法的算法核心是贪心以及动态规划

算法思想

1.插点法

设顶点 vi(开始顶点) 到顶点 vj(结束顶点) 的当前最短路径距离为l_{ij},此时我们找到一个顶点 vk(中间顶点) ,且 vk 同时与 vivj 相通,此时我们计算 vivk 的距离 l_{ik}vkvj 的距离l_{kj},若(l_{ik}+l_{kj})<l_{ij}就说明顶点vk的插入可以使得顶点 vi 到顶点 vj 的路径距离变短,那么我们就更新顶点 vi 到顶点 vj 的当前最短路径距离为 (l_{ik}+l_{kj})。

2.辅助数组

该算法还需要设置两个数组用来辅助,分别是distance[][]path[][]

distance[i][j]:储存顶点i到顶点j的最短距离(用来存储最短路径的距离)

path[i][j]:用来储存顶点i到顶点j的最短路径前驱节点(用来存储最短路径)

tips:在这里我觉得有必要解释一下path[][]中前驱节点的意思,如下图所示

 如果求顶点0到顶点3这条路径的前驱节点,因为顶点2在终点3之前,因此顶点2就是前驱节点,但是在实际代码编程中,有时候会发生前驱顶点到了前面的情况,但是并不会影响最短路径距离的计算

初始化distance[i][j]:将其初始化为该图以邻接矩阵形式存储的形式,其中不直接相连的位置最短距离初始化为无穷大∞(在编程中可以define infinity为一个很大的数用来表示无穷大),直接相连的位置其最短距离就初始化为该边上的权值,自己与自己的最短距离初始化为0

初始化path[][]:当一个顶点到达另一个顶点没有确定的最短路径时,我们就设置顶点i到顶点j的前驱节点,也就是path[i][j]为 i,也就是初始化顶点0到其他任何顶点(包括自己)的前驱节点为自身本身

3.三层循环

Floyd算法的全局是,将每个顶点都作为中间顶点进行插入,并不断更改开始顶点和结束顶点,若有符合结果的就更新distance[][]path[][]的值,直到所有顶点都作为中间顶点并全部遍历过一次后算法结束。因此三层循环由外到内分别是 中间顶点、开始顶点、结束顶点。

 

算法举例

如上图所示是一个无向连通网,我们先对两个数组进行初始化得到下图:

然后我们从顶点0开始,此时0顶点作为中间顶点,我们对其他的所有顶点进行遍历,找到连通的顶点,我们先找到顶点1,此时顶点1就成为了开始顶点,然后我们继续寻找找到顶点2作为结束顶点,此时我们可以知道distance[1][2]=infinity,distance[1][0]+distance[0][2]=12,则有distance[1][0]+distance[0][2]<distance[1][2],那我们就更新distance[1][2]为12,并且顶点0作为这个新的当前最短路径中的一个中间顶点,我们就更新path[1][2]=0

 更新完毕之后,此时结束顶点作为最内层循环,继续查找下一个连通的顶点,找到了顶点6(注意,在程序中是会运行到以自己为开始顶点和结束顶点的情况的,但是这种情况并不会影响结果,因此我们只需要关注不同的顶点的情况即可),此时顶点6就作为结束顶点,distance[1][6]=3,distance[1][0]+distance[0][6]=7,distance[1]<distance[1][0]+distance[0][6],无需更新distance[1][6]和path[1][6]

此时我们已经可以发现已经没有其他的结束顶点是连通的了,那我们就进入第二层循环也就是开始顶点的循环,我们找到了顶点2作为开始顶点,然后再次进入结束顶点的循环,我们找到了顶点1,(此时我们可以发现这一步跟上面所展示的第一步很像,是因为对于无向图来说是双向的),我们依葫芦画瓢可以得到新的distance[2][1]=12,新的path[2][1]=0

就这样以此类推,直到最外层循环也就是中间顶点循环执行完毕。

算法实现

1.将图使用邻接矩阵进行储存,我这里为了方便测试,直接将图输入在了类中,当然为了适应其他的图,我也写了一个创建图的函数createundigraph()

无向图类:

class undigraph
{
    public:
    //int maxvertex;
    int maxvertex=7;
    //图以邻接矩阵的形式存储
    //int adjmatrix[maxsize][maxsize];
    int adjmatrix[7][7]=
    {
        {0,5,7,infinity,infinity,infinity,2},
        {5,0,infinity,9,infinity,infinity,3},
        {7,infinity,0,infinity,8,infinity,3},
        {infinity,9,infinity,0,infinity,4,infinity},
        {infinity,infinity,8,infinity,0,5,4},
        {infinity,infinity,infinity,4,5,0,6},
        {2,3,3,infinity,4,6,0}
    };
    int distance[maxsize][maxsize];//两点间最短距离数组
    int path[maxsize][maxsize];//两点间最短距离路径数组
    //创建图
    void createundigraph();
    //初始化
    void initdistance();
    void initpath();
    //打印数组
    void printdistance();//打印距离数组
    void printpathmatrix();//打印最短路径矩阵
    void printpath();//打印最短路径
    //最短路径算法
    void floyd();
};

2.distance[][]path[][]进行初始化

distance[][]初始化函数:

void undigraph::initdistance()
{
    for (int i = 0; i < maxvertex; i++)
    {
        for (int j = 0; j < maxvertex; j++)
        {
            distance[i][j]=adjmatrix[i][j];//使其与图的邻接矩阵相同 
        }
    }
}

path[][]初始化函数:

void undigraph::initpath()
{
    for (int i = 0; i < maxvertex; i++)
    {
        for (int j = 0; j < maxvertex; j++)
        {
            path[i][j]=i;//将前驱顶点初始化为自身
        }
    }
}

3.调用Floyd算法

void undigraph::floyd()
{
    for (int i = 0; i <maxvertex ; i++)
    { 
        for (int j = 0; j <maxvertex ; j++)
        {
            for (int k = 0; k <maxvertex ; k++)
            {
                int startdistance=distance[j][i];
                int enddistance=distance[i][k];
                if ((startdistance+enddistance)<distance[j][k])
                {
                    distance[j][k]=startdistance+enddistance;
                    path[j][k]=i;
                }  
            }
        }
    }  
}

4.全部代码

#include<stdio.h>
#include<iostream>
#define infinity 999
#define maxsize 100
using namespace std;

class undigraph
{
    public:
    //int maxvertex;
    int maxvertex=7;
    //图以邻接矩阵的形式存储
    //int adjmatrix[maxsize][maxsize];
    int adjmatrix[7][7]=
    {
        {0,5,7,infinity,infinity,infinity,2},
        {5,0,infinity,9,infinity,infinity,3},
        {7,infinity,0,infinity,8,infinity,3},
        {infinity,9,infinity,0,infinity,4,infinity},
        {infinity,infinity,8,infinity,0,5,4},
        {infinity,infinity,infinity,4,5,0,6},
        {2,3,3,infinity,4,6,0}
    };
    int distance[maxsize][maxsize];//两点间最短距离数组
    int path[maxsize][maxsize];//两点间最短距离路径数组
    //创建图
    void createundigraph();
    //初始化
    void initdistance();
    void initpath();
    //打印数组
    void printdistance();//打印距离数组
    void printpathmatrix();//打印最短路径矩阵
    void printpath();//打印最短路径
    //最短路径算法
    void floyd();
};

void undigraph::createundigraph()
{
    
    int tempdata;
    int adjvertex;
    int i=0;
    for(;;i++)
    {
        cout<<"请输入顶点"<<i<<"数据:"<<endl;
        cin>>tempdata;
        if (tempdata==-1)
        {
            break;
        }
        
        cout<<"请输入该顶点的相邻顶点:"<<endl;
        cin>>adjvertex;
        if (adjvertex>=maxvertex)
        {
            maxvertex=adjvertex+1;
        }
        
        adjmatrix[i][adjvertex]=1;
        adjmatrix[adjvertex][i]=1;
    }
    cout<<"邻接矩阵构建完毕"<<endl;
    
}

void undigraph::initdistance()
{
    for (int i = 0; i < maxvertex; i++)
    {
        for (int j = 0; j < maxvertex; j++)
        {
            distance[i][j]=adjmatrix[i][j];   
        }
    }
}

void undigraph::initpath()
{
    for (int i = 0; i < maxvertex; i++)
    {
        for (int j = 0; j < maxvertex; j++)
        {
            path[i][j]=i;
        }
    }
}

void undigraph::printdistance()
{
    cout<<"==========distance=========="<<endl;
    for (int i = 0; i < maxvertex; i++)
    {
        for (int j = 0; j < maxvertex; j++)
        {
            cout<<distance[i][j]<<"  ";
        }
        cout<<endl; 
    }
}

void undigraph::printpathmatrix()
{
    cout<<"==========path=========="<<endl;
    for (int i = 0; i < maxvertex; i++)
    {
        for (int j = 0; j < maxvertex; j++)
        {
            cout<<path[i][j]<<"  ";
        }
        cout<<endl; 
    }
}

void undigraph::printpath()
{
    cout<<"请输入起点顶点:"<<endl;  
    int startvertex;
    cin>>startvertex;
    cout<<"请输入结束顶点:"<<endl;
    int endvertex;
    cin>>endvertex;
    int tempvertex=startvertex;
    int frontvertex=path[startvertex][endvertex];;
    cout<<startvertex<<"---->";
    while (true)
    {
        if (tempvertex!=frontvertex)
        {
                cout<<frontvertex<<"---->";
                tempvertex=frontvertex;
                frontvertex=path[frontvertex][endvertex]; 
        }
        else
        {
            cout<<endvertex<<endl;
            break;
        }
        
    }
    
}

void undigraph::floyd()
{
    for (int i = 0; i <maxvertex ; i++)
    { 
        for (int j = 0; j <maxvertex ; j++)
        {
            for (int k = 0; k <maxvertex ; k++)
            {
                int startdistance=distance[j][i];
                int enddistance=distance[i][k];
                if ((startdistance+enddistance)<distance[j][k])
                {
                    distance[j][k]=startdistance+enddistance;
                    path[j][k]=i;
                }  
            }
        }
    }  
}


int main()
{
    undigraph ug;
    ug.initdistance();
    ug.initpath();
    ug.printdistance();
    ug.printpathmatrix();
    ug.floyd();
    ug.printdistance();
    ug.printpathmatrix();
    ug.printpath();
    return 0;
}

代码执行结果

三、总结

个人认为相对于dijkstra算法来说,floyd算法的代码更具有可读性和便于理解,但是dijkstra算法因其每轮迭代都可以得到一个点的最短路径的稳定性,在实际解决问题时我个人更加推荐,因此需要对该算法的代码进行加深一步理解,如果觉得本文写的不错的请点点赞,有写得不好的请多多指正。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

臭刘皮

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

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

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

打赏作者

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

抵扣说明:

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

余额充值