标题:发一些编写过的带权有向图的最短路径,以及拓扑排序的算法实现(算法都集成在 ...
只看楼主
geninsf009
Rank: 3Rank: 3
等 级:论坛游民
威 望:8
帖 子:613
专家分:95
注 册:2008-8-16
结帖率:100%
 问题点数:0 回复次数:9 
发一些编写过的带权有向图的最短路径,以及拓扑排序的算法实现(算法都集成在GraphDirect<T,E>类中)
首先是带权有向图的GraphDirect<T,E>类的基本实现:
所有代码请指教,需要的朋友也可以参考,总之大家共勉。
相信这些算法也是大家最常见的了。

#ifndef GRAPHICDIRECT_H
#define GRAPHICDIRECT_H

#include<iostream.h>
#include<stdlib.h>
#include"SeqStack.h"
const int DefaultVertices=30;
const int maxWeight=100000;

/////////////////////////////////////////////////
//Edge 边结点结构的定义
/////////////////////////////////////////////////
template<class T,class E>
struct Edge
{
    int dest;             //目的结点的结点号
    E cost;               //边对应的权值
    Edge<T,E>* link;      //下一条边结点的指针

    Edge(){};             //默认构造函数
    Edge(int num,E weight)//带参数的构造函数
    :dest(num),cost(weight),link(NULL){};
    bool operator!=(Edge<T,E>& R)const
    {                     //成员重载运算符
        if(R.dest==dest)  //判断两个边结点是否相等
            return true;
        else
            return false;
    };
};
///////////////////////////Edge边结点结构定义结束

/////////////////////////////////////////////////
//Vertex结构 图的顶点结点的定义
/////////////////////////////////////////////////
template<class T,class E>
struct Vertex
{
    T data;        //结点的数据域
    Edge<T,E>* adj;//边链表的首指针
};
///////////////////////////Vertex顶点结构定义结束

/////////////////////////////////////////////////
//Graphlink<T,E>类模板的实现
//以邻接链表方式实现的带权有向图的类模
//板的声明和实现T是顶点数据域类型,E是边的权值类型
/////////////////////////////////////////////////
template<class T,class E>
class GraphDirect
{
private:
    Vertex<T,E>* NodeTable;                 //顶点表数组
    bool* visited;                          //用于标记第i个结点是否被访问的数组
   
    int maxVertices;                        //图中最大的顶点个数
    int numEdges;                           //当前边的条数
    int numVertices;                        //当前顶点的个数
public:
    GraphDirect(int sz=DefaultVertices);    //默认构造函数
    ~GraphDirect();                         //析构函数

    bool insertVertex(const T vertex);      //在图中插入一个顶点vertex
    bool insertEdge(int v1,int v2,E cost);  //插入一条边以及对应的权值
    E getWeight(int v1,int v2);             //取得边(v1,v2)上的权值

    bool TopologicalSort();                 //对当前的有向图进行拓扑排序
    void Dijkstra(int v);                   //迪吉斯特拉算法求单源最短路径
    void Bellman(int v);                    //带负权值单源有向图最短路径求解
    void DisplayPath(int v,E* d,int* pa);   //显示path数组中保存的最短路径
    void Floyd();                           //求所有顶点之间的最短路径

    //友元重载输出运算符
    friend ostream& operator<<(ostream& os,GraphDirect<T,E>& G);
};
/////////////////////Graphlink<T,E>类模板声明结束

/////////////////////////////////////////////////
//构造函数   构造一个空的有向图的类
/////////////////////////////////////////////////
template<class T,class E>
GraphDirect<T,E>::GraphDirect(int sz)
{
    maxVertices=sz;                         //初始化相关数据
    numEdges=0;
    numVertices=0;
    NodeTable=new Vertex<T,E>[maxVertices]; //分配顶点数组的空间
    if(NodeTable==NULL)                     //如果内存分配失败
    {
        cout<<"顶点数组内存分配失败!"<<endl;
        exit(1);
    };
    for(int i=0;i<maxVertices;i++)          //把所有的结点的边链表链接指针清NULL
        NodeTable[i].adj=NULL;
};
/////////////////////////////////////构造函数结束

/////////////////////////////////////////////////
//析构函数  释放所有所有结点和边链表的恶内存空间
/////////////////////////////////////////////////
template<class T,class E>
GraphDirect<T,E>::~GraphDirect()
{
    for(int i=0;i<numVertices;i++)          //释放所有的边链表的内存
    {
        Edge<T,E>* ptr=NodeTable[i].adj;    //获取每个边链表的头指针
        Edge<T,E>* pre;
        while(ptr!=NULL)
        {
            pre=ptr;
            ptr=ptr->link;
            delete pre;
        };
    };
    delete [] NodeTable;                    //再释放结点数组的内容
};
/////////////////////////////////////析构函数结束

/////////////////////////////////////////////////
//insertVertex()公有成员函数
//在当前的代权有向图中插入一个结点
//T是结点的恶数据类型
/////////////////////////////////////////////////
template<class T,class E>
bool GraphDirect<T,E>::insertVertex(T x)
{
    if(numVertices==maxVertices)            //如果当前的顶点数组已经满了
        return false;                       //插入失败
    NodeTable[numVertices].data=x;
    numVertices++;                          //顶点的个数增加一个
    return true;                            //插入成功
};
///////////////////////////insertVertex()函数结束

/////////////////////////////////////////////////
//insertEdge()公有成员函数  插入一条有向边
//其中参数i是前驱顶点编号,j是后继结点编号,weight是权
/////////////////////////////////////////////////
template<class T,class E>
bool GraphDirect<T,E>::insertEdge(int i,int j,E weight)
{
    if(i>=numVertices || j>=numVertices)    //结点号错误
        return false;
    Edge<T,E>* ptr=NodeTable[i].adj;        //ptr指向结点i的边链表头
    Edge<T,E>* newEdge
        =new Edge<T,E>(j,weight);           //新建一个边结点
    if(ptr==NULL)                           //如果边链表是空的
    {
        NodeTable[i].adj=newEdge;           //就直接挂入
        numEdges++;
        return true;                        //插入成功
    };

    while(ptr->link!=NULL)                  //让ptr指向边链表尾
        ptr=ptr->link;
    ptr->link=newEdge;                      //在边链表尾插入边结点
    numEdges++;
    return true;
};
/////////////////////////////insertEdge()函数结束

/////////////////////////////////////////////////
//getWeight()公有成员函数
//得到指定边(v1,v2)上的权值
/////////////////////////////////////////////////
template<class T,class E>
E GraphDirect<T,E>::getWeight(int v1,int v2)
{
    Edge<T,E>* ptr=NodeTable[v1].adj;//得到v1结点的边链表的首指针
    if(v1==v2)                       //如果是自环
        return 0;
    while(ptr!=NULL)                 //遍历该边链表找结点v2
    {
        if(ptr->dest==v2)            //如果找到就返回
            return ptr->cost;
        ptr=ptr->link;
    }
    return maxWeight;                //即这两点之间不存在边
};
//////////////////////getWeight()公有成员函数结束

/////////////////////////////////////////////////
//友元重载<<输出运算符
/////////////////////////////////////////////////
template<class T,class E>
ostream& operator<<(ostream& os,GraphDirect<T,E>& G)
{
    cout<<"当前图的内容是:"<<endl;
    Edge<T,E>* ptr;
    for(int i=0;i<G.numVertices;i++)
    {
        cout<<"第"<<i<<"个顶点"<<T(G.NodeTable[i].data)<<"边结点:";
        ptr=G.NodeTable[i].adj;
        while(ptr!=NULL)
        {
            cout<<"("<<ptr->dest<<","<<ptr->cost<<")"<<" ";
            ptr=ptr->link;
        };
        cout<<endl;
    };
    cout<<"当前图的总结点数是:"<<G.numVertices<<endl;
    cout<<"当前图的总边的个数:"<<G.numEdges<<endl;
    return os;
};
///////////////////////////////////<<友元重载结束

[[it] 本帖最后由 geninsf009 于 2008-11-29 19:31 编辑 [/it]]
搜索更多相关主题的帖子: 算法 有向图 GraphDirect 拓扑 路径 
2008-11-29 19:19
geninsf009
Rank: 3Rank: 3
等 级:论坛游民
威 望:8
帖 子:613
专家分:95
注 册:2008-8-16
得分:0 
用堆栈实现的拓扑排序(其实我觉得用队列也可以实现):
/////////////////////////////////////////////////
//TopologicalSort()公有成员函数
//对当前的有向图进行拓扑排序,输出一个拓扑序列
/////////////////////////////////////////////////
template<class T,class E>
bool GraphDirect<T,E>::TopologicalSort()
{
    /*定义辅助存储空间,并进行数据初始化*/
    int* inDegree=new int[numVertices];  //统计每个结点入度的数组
    SeqStack<int> S(numVertices);        //用于存放入度为0的结点的编号
    for(int i=0;i<numVertices;i++)
        inDegree[i]=0;                   //初始化入度统计数组

    /*首先遍历结点数组找出每个结点的入度*/
    for(i=0;i<numVertices;i++)
    {
        Edge<T,E>* ptr=NodeTable[i].adj; //取得每个边链表的第一个元素结点
        while(ptr!=NULL)                 //遍历每个边链表的结点
        {
            int p=ptr->dest;             //统计图中每个顶点入度数
            inDegree[p]++;
            ptr=ptr->link;
        };
    };

    /*找出入度为0的结点并把对应结点号入栈*/
    for(i=0;i<numVertices;i++)
        if(inDegree[i]==0)               //如果结点的入度是0
            S.Push(i);                   //把该结点入栈

    /*从初始的堆栈开始进行拓扑排序输出序列*/
    int count=0;                         //计数器
    cout<<"当前图的拓扑线性序列是:";
    while(!S.IsEmpty())
    {
        int k;
        S.Pop(k);                        //从堆栈中弹出一个结点
        cout<<NodeTable[k].data<<" ";    //输出该结点
        count++;
        Edge<T,E>* ptr=NodeTable[k].adj; //得到k的边链表的头结点
        while(ptr!=NULL)                 //把所有与结点k关联的结点的入度减1
        {
            int p=--inDegree[ptr->dest]; //关联的结点入度减去1
            if(p==0)                     //如果入度已经是0
                S.Push(ptr->dest);       //把入度为0结点压入堆栈    
            ptr=ptr->link;  
        };
    };
    cout<<endl;
    /*判断图中是否存在有向环*/
    delete [] inDegree;                  //释放入度辅助数组的空间
    if(count==numVertices)               //如果输出的结点个数等于总结点数
        return true;
    else                                 //否则说明图中存在环
    {
        cout<<"图中存在环!"<<endl;
        return false;
    };
};
////////////////////////TopologicalSort()函数结束
2008-11-29 19:20
geninsf009
Rank: 3Rank: 3
等 级:论坛游民
威 望:8
帖 子:613
专家分:95
注 册:2008-8-16
得分:0 
迪吉斯特拉算法:
/////////////////////////////////////////////////
//Dijkstra()公有成员函数,Dijkstra求最短路径的算法
//求当前图中从结点v出发到其它所有顶点的路径长度
//T是关键码的数据类型,E是路径长度的数据类型
/////////////////////////////////////////////////
template<class T,class E>
void GraphDirect<T,E>::Dijkstra(int v)
{
    /*初始化工作*/
    int n=numVertices;         //图的当前顶点的个数
    E* dist=new E[n];          //定义存放最短路径长度的数组
    int* Path=new int[n];      //开辟最短路径数组的空间
    for(int i=0;i<n;i++)
        dist[i]=maxWeight;
    bool* s=new bool[n];       //当前已经求出最短路径的顶点集合
    for(i=0;i<n;i++)
        s[i]=false;            //初始并未全部加入
    if(v>=n)                   //如果开始的结点号溢出
        return;
    for(i=0;i<=n-1;i++)        //对dist[]数组进行初始化
    {
        dist[i]=getWeight(v,i);//把所有开始与v相邻接的边的长度加入dist[]
        if(dist[i]<maxWeight
            && dist[i]!=0)     //把当前处理的顶点的前驱顶点放入Path[]数组   
            Path[i]=v;
        else
            Path[i]=-1;
    }
    s[v]=true;                 //先把结点v加入到集合中去
    dist[v]=0;
    /*依次把图中其他
    的结点加入s求
    出他它们的最短
    路径*/
    int count=1;
    int min;                   //求当前dist[]数组中的最小值
    int k;                     //存放当前从dist[]找出的最小值的标号
    for(int p=1;p<=n-1;p++)    //一共有n-1个顶点要陆续加入到s中
    {
        int isfirst=1;
        /*找要加入s的结点即
        当dist[]中权最小点*/
        for(i=0;i<=n-1;i++)    //遍历dist[]数组
        {
            if(s[i]!=true
                && isfirst==1) //如果结点i还没有加进s
            {
                min=dist[i];
                k=i;
                isfirst=0;
                continue;
            };
            if(s[i]!=true && dist[i]<min)
            {
                min=dist[i];   //找权值最小的及其标号
                k=i;
            };
        };
        s[k]=true;             //把找到的k加入到s中去
        /*修改dist[]数组的值*/
        for(i=0;i<=n-1;i++)
        {
            if(s[i]!=true)     //如果结点i还没有被加入s
            {                  //则需要被修改
                if(dist[i]>dist[k]+getWeight(k,i))
                {
                    dist[i]=dist[k]+getWeight(k,i);
                    Path[i]=k; //把顶点i的当前最短路径上的前驱加入Path[]中
                };
            };
        };
    };
    /*显示生成的每条最短路径*/
    DisplayPath(v,dist,Path);
    /*释放辅助的存储空间*/
    delete [] dist;
    delete [] Path;
};
///////////////////////////////Dijkstra()函数结束
2008-11-29 19:21
geninsf009
Rank: 3Rank: 3
等 级:论坛游民
威 望:8
帖 子:613
专家分:95
注 册:2008-8-16
得分:0 
权值允许是负值的贝尔曼-福德最短路径算法:
/////////////////////////////////////////////////
//Bellman()公有成员函数  
//带负权值的单源最短路径的求解问题
//Bellman_Ford算法要求求解的图中不能有带负权值
//的回路,否则将会出现无穷小的所谓最短路径
/////////////////////////////////////////////////
template<class T,class E>
void GraphDirect<T,E>::Bellman(int v)
{
    /*初始化工作*/
    int n=numVertices;
    E* dist=new E[n];          //创建存放最短路径的数组
    int* path=new int[n];      //存放最短路径的辅助数组
    for(int i=0;i<n;i++)       //对dist[]数组进行第一轮填写
    {
        E w=getWeight(v,i);    //得到v->i上的权值
        dist[i]=w;            
        if(i!=v &&             //对path[]数组进行第一轮填写
            dist[i]<maxWeight)
            path[i]=v;
        else
            path[i]=-1;        //v不是i的直接前驱
    };

    /*对dist[]向量
    进行n-1次迭代
    计算最后得最短
    路径长度*/
    for(i=1;i<=n-1;i++)
    {
        for(int k=0;k<n;k++)//进行一轮dist[]值的修改
        {
            if(k!=v)        //除了开始顶点v以外
            {
                int isfirst=1;
                E min;int p;
                for(int j=0;j<n;j++)
                {
                    if(isfirst==1)
                    {
                        min=dist[j]+getWeight(j,k);
                        isfirst=0;
                        continue;
                    };      //求出min{dist[j]+getWeight(j,k)}
                    if(min>dist[j]+getWeight(j,k))
                    {
                        min=dist[j]+getWeight(j,k);
                        p=j;//记录权值最小的边的出发点
                    }
                };
                if(dist[k]>min)
                {
                    dist[k]=min;
                    path[k]=p;
                };
            }
        };
    };
    /*显示path数组中的最短路径*/
    DisplayPath(v,dist,path);
    /*释放辅助的存储空间*/
    delete [] dist;
    delete [] path;
};
////////////////////////////Bellman()算法函数结束

[[it] 本帖最后由 geninsf009 于 2008-11-29 19:24 编辑 [/it]]
2008-11-29 19:22
geninsf009
Rank: 3Rank: 3
等 级:论坛游民
威 望:8
帖 子:613
专家分:95
注 册:2008-8-16
得分:0 
读取辅助数组path[]中的最短路径的算法,
path[i]数组实质存放的是i在最短路径中的直接前驱:
/////////////////////////////////////////////////
//DisplayPath()公有成员函数
//显示当前图的从定点v到其他定点的所有最短路径
//即把当前Path数组中的最短路径显示出来
/////////////////////////////////////////////////
template<class T,class E>
void GraphDirect<T,E>::DisplayPath(int v,E* d,int* path)
{
    int n=numVertices;
    for(int i=0;i<n;i++)       //遍历除v以外的所有的顶点
    {
        if(i!=v)               //显示从v到i的最短路径
        {
            cout<<"从"<<i<<"到"<<v<<"的最短路径:";
            cout<<"其长度是:"<<d[i]<<';'<<"路径是:";
            int p=i;           //游标指针从v开始
            cout<<'{'<<p<<"<-";
            do
            {
                cout<<path[p]; //显示最短路径的前驱
                p=path[p];
                if(p!=v)
                    cout<<"<-";//路径上的最后一个结点不需要显示','
            }while(p!=v);      //到i表示循环结束
            cout<<'}';
        };
        cout<<endl;
    };
};
////////////////////////////DisplayPath()函数结束
2008-11-29 19:23
geninsf009
Rank: 3Rank: 3
等 级:论坛游民
威 望:8
帖 子:613
专家分:95
注 册:2008-8-16
得分:0 
弗洛伊德最短路径算法:
注意:本代码中起点和终点需要你手动输入,
/////////////////////////////////////////////////
//Floyd()公有成员函数
//弗洛伊德算法:以邻接矩阵为基础求出当前图的所有的
//两两定点之间的最短路径及其长度,算法思想:对矩阵
//进行n轮的修改,每次修改加入一个中间结点缩短距离
/////////////////////////////////////////////////
template<class T,class E>
void GraphDirect<T,E>::Floyd()
{
    /*初始化工作*/
    int n=numVertices;          //当前图的顶点个数
    E** A=new E*[n];            //用于存放两两最短路径的动态数组
    int** path=new int*[n];     //存放路径的数组
    for(int i=0;i<n;i++)      
    {
        A[i]=new E[n];
        path[i]=new int[n];
    };
    for(i=0;i<n;i++)            //初始化最短路径长度矩阵
    {
        for(int j=0;j<n;j++)
        {
            A[i][j]=getWeight(i,j);
            if(i!=j && A[i][j]<maxWeight)
                path[i][j]=i;
            else
                path[i][j]=-1;
        };
    };
    /*进行n轮的寻找
    每次插入一个结点*/
    for(int k=0;k<n;k++)        //每轮中间插入一个顶点
    {
        for(int p=0;p<n;p++)    //遍历矩阵A[][]所有元素,修改其值
        {
            for(int q=0;q<n;q++)
            {
                if(A[p][q]>A[p][k]+A[k][q])
                {               //如果A[i][j]更小,就替换之
                    A[p][q]=A[p][k]+A[k][q];
                    path[p][q]  //缩短最短路径,绕过k,k是刚插入的中间顶点
                        =path[k][q];
                };
            };
        };
    };
    /*读取path[][]中的最短路径
    注:path[i][j]中存放的是最短
    路径中顶点j的前驱结点, 初始
    情况下path[i][j]==i*/
    int x,y;
    cout<<"请输入起点:";
    cin>>x;
    cout<<"请输入终点:";
    cin>>y;
    if(x>=n || y>=n)
    {
        cout<<"输入的顶点号越界!"<<endl;
        return;
    };
    cout<<"从顶点"<<x<<"到"<<y
        <<"的最短路径长度是:"
        <<A[x][y]<<endl;
    if(x==y)                    //如果起点和终点重合
        return;                 //直接返回
    cout<<"该最短路径是:"<<"{";
    int p=y;
    do                          //从path[][]中读取最短路径
    {
        cout<<p<<"<-";
        p=path[x][p];
    }while(p!=x);
    cout<<x<<'}';
};
//////////////////////////////////Floyd()函数结束

#endif
2008-11-29 19:25
geninsf009
Rank: 3Rank: 3
等 级:论坛游民
威 望:8
帖 子:613
专家分:95
注 册:2008-8-16
得分:0 
测试以上代码的main()函数,已经通过调试可以
直接运行:

#include"GraphicDirect.h"

//////////////////////////////////////////////////////
//main()函数   测试通过连接表实现的带权有向图
//////////////////////////////////////////////////////
int main()
{
    GraphDirect<char,int>  GL(20);
   
    GL.insertVertex('0');
    GL.insertVertex('1');
    GL.insertVertex('2');
    GL.insertVertex('3');

    GL.insertEdge(0,1,1);
    GL.insertEdge(0,3,4);
    GL.insertEdge(1,2,9);
    GL.insertEdge(1,3,2);
    GL.insertEdge(2,0,3);
    GL.insertEdge(2,1,5);
    GL.insertEdge(2,3,8);
    GL.insertEdge(3,2,6);

    cout<<GL<<endl;
    cout<<"Dijkstra算法求出的最短路径:";
    GL.Dijkstra(0);
    cout<<endl<<"贝尔曼算法求最短路径:";
    GL.Bellman(0);
    cout<<endl<<"Floyd算法求任意点之间最短路径:"<<endl;
    GL.Floyd();
    cout<<endl;

    return 0;
};
////////////////////////////////////////main()函数结束
2008-11-29 19:27
geninsf009
Rank: 3Rank: 3
等 级:论坛游民
威 望:8
帖 子:613
专家分:95
注 册:2008-8-16
得分:0 
上面有个#include"SeqStack.h"头文件里的代码由于篇幅过大,就不贴出来了,因为是类模板,类模板不同于类的是,类模板只编译那些在main()调用的成员函数,不调用的成员函数就不编译了,只作外部语法检查,所以,这里的几个算法,除了拓扑排序,几个最短路径算法都可以正常运行。
2008-11-29 19:42
geninsf009
Rank: 3Rank: 3
等 级:论坛游民
威 望:8
帖 子:613
专家分:95
注 册:2008-8-16
得分:0 
另外,我写的这个代权有向图是基于临界表,单仍然可以用于邻接矩阵,只要你写一个getWeight()成员函数作为通用接口,或者写一个转换构造函数,就可以了,我在Graphmtx<T,E>类中就是这么做的。大家参考。
2008-11-29 19:45
鬼手刀客
Rank: 1
等 级:新手上路
帖 子:57
专家分:0
注 册:2008-10-19
得分:0 
刚好学到这里,非常感谢!dddddddddd!
2008-11-29 22:01



参与讨论请移步原网站贴子:https://bbs.bccn.net/thread-246930-1-1.html




关于我们 | 广告合作 | 编程中国 | 清除Cookies | TOP | 手机版

编程中国 版权所有,并保留所有权利。
Powered by Discuz, Processed in 0.155022 second(s), 7 queries.
Copyright©2004-2024, BCCN.NET, All Rights Reserved