本文介绍了有向加权图的两种实现方式:二维数组和链表,包括基本操作如插入、删除边等,并提供了广度优先搜索和深度优先搜索算法的实现。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

图应该是计算机应用的一个比较重要的方向吧,数据结构与算法c++语言描述里面单独拿出一章用来讲图。
不过这本书讲的比较浅显且不全面,如果想深入的研究图的知识,建议去学习下图论,不过图论自学起来有点难,建议去大学蹭课。
下面给出有向加权图的二维矩阵实现方法:

template<class T>
class graph
{
    public:
    virtual ~graph(){}
    virtual int numberOfVertices() const=0;
    virtual int numberOfEdges() const=0;
    virtual bool existsEdge(int,int)const=0;
    virtual void insertEdge(edge<T>*)=0;
    virtual void eraseEdge(int,int)=0;
    virtual int degree(int) const=0;
    virtual int inDegree(int) const=0;
    virtual int outDegree(int) const=0;

    virtual bool directed() const=0;
    virtual bool weighted() const=0;
    virtual vertexIterator<T>* iterator(int)=0;
};


template<class T>
class adjacencyWdigraph:public graph<T>
{
    protected:
    int n;//顶点个数
    int e;//边的个数
    T **a;//邻接数组
    T noEdge;//表示不存在的边
public:
    adjacencyWdigraph(int numberOfVertices=0,T theNoEdge=0)//初始化函数,构造一个数据全是noEdge的矩阵
    {//构造函数
        //确定顶点的合法性
        if(numberOfVertices<0)
            throw illegalParameterValue("number of vertices must be >=0");
        n=numberOfVertices;
        e=0;
        noEdge=theNoEdge;
        make2dArray(a,n+1,n+1);//构造一个二维矩阵,这个函数我们之前写过
        for(int i=1;i<=n;i++)//将二维矩阵里面的元素全部初始化为noEdge
            fill(a[i],a[i]+n+1,noEdge);
    }
    ~adjacencyWdigraph(){delete2dArray(a,n+1);}//析构掉初始化函数里面的堆栈空间
    int numberOfVertices() const {return n;}
    int numberOfEdges() const {return e;}
    bool directed() const {return true;}
    bool weighted() const {return true;}
    bool existsEdge(int i,int j)const
    {//返回值是真,当且仅当(i,j)是图的一条边
    if(i<1||j<1||i>n||j>n||a[i][j]==noEdge)
        return false;
    else
        return true;    
    }
    void insertEdge(edge<T>*theEdge)//edge模板类有三个函数分别获取边的两个点和权重
    {//插入边;如果该边已经存在,则用theEdge->weight()修改边的权
    int v1=theEdge->vertex1();
    int v2=theEdge->vertex2();
    if(v1<1||v2<1||v1>n||v2>n||v1==v2)
    {
        ostringstream s;
        s<<"("<<v1<<","<<v2<<") is not a permissible edge";
        throw illegalParameterValue(s.str());
    }
    if(a[v1][v2]==noEdge)
        e++;
    a[v1][v2]=theEdge->weight();    
    }
    void eraseEdge(int i,int j)
    {//删除边(i,j)
    if(i>1||j>1||i<n||j<n||a[i][j]!=noEdge)
    {
    a[i][j]=noEdge;
    e--;
    }
    }
    void checkVertex(int theVertex)const
    {//确认是有效顶点
    if(theVertex<1||theVertex>n)
    {
    ostringstream s;
    s<<" no vertex"<<theVertex;
    throw illegalParameterValue(s.str());
    }
    }
    int degree(int theVertex)const
    {
        throw undefinedMethod("degree() undefined");//这里定义的是一个有向加权图,所以没有度
    }
    int outDegree(int theVertex)const
    {//返回顶点theVertex的出度
        checkVertex(theVertex);
        int sum=0;
        for(int j=1;j<=n;j++)
            if(a[theVertex][j]!=noEdge)//以j为列的点的和为出度
                sum++;
        return sum;
    }

    int inDegree(int theVertex)const
    {//返回顶点theVertex的出度
        checkVertex(theVertex);
        int sum=0;
        for(int j=1;j<=n;j++)
            if(a[j][theVertex]!=noEdge)//以j为列的点的和为出度
                sum++;
        return sum;
    }
    class myIterator:public vertexIterator<T>
    {
        public:
        myIterator(T* theRow,T theNoEdge,int numberOfVertices)
        {
            row=theRow;
            noEdge=theNoEdge;
            n=numberOfVertices
            currentVertex=1;
        }
        ~myIterator(){}
        int next(T& theWeight)//有点像python的生成器
        {//返回下一个顶点。若不存在,则返回0
        //赋权值theWight=边的权值
        //寻找下一个邻接的顶点
        for(int j=currentVertex;j<=n;j++)
        if(row[j]!=noEdge)//我们的矩阵为n+1阶所以第零行和第零列是不存储数据的,如果输入的顶点不关联任何边即其权值为noEdge
        {
            currentVertex=j+1;
            theWeight=row[j];
            return j;//返回当前顶点及其权值,currentVertex+1
        }
        //不存在下一个邻接的顶点
        currentVertex=n+1;//否则返回0退出函数
        return 0;

        }
        protected:
        T* row;                           //邻接矩阵的行
        T noEdge;                         //当且仅当没有关联的边
        int n;                            //顶点数
        int currentVertex;
    };
    myIterator* iterator(int theVertex)
    {//返回顶点theVetex的迭代器
    checkVertex(theVertex);
    return new myIterator(a[theVertex],noEdge,n);       
    }
}

其实上述代码在你理解了图之后是非常简单的,只是最后的那个迭代部分需要提一下,最后的函数之所以用new在堆栈中分配内存,是为了让迭代器在使用后不会被自动析构,而currentVetex不会被重置,这样我们就能连续的使用next每次都以上一个点为起始点。

class linkedDigraph:public graph<bool>
{
    protected:
    int n;
    int e;
    graphChain<int> *aList;//邻接表
    public:
    linkedDigraph(int numberOfVertices=0)
    {
        if(numberOfVertices<0)
            throw illegalParameterValue("number of vertexs must be >=0");
        n=numberOfVertices;
        e=0;
        aList=new graphChain<int> [n+1];//创建一个数组,每个数组元素都是一个链表
    }
    ~linkedDigraph(){delete [] aList;}
    //关于numberOfVertexs,numberOfEdges,directed和weighted的代码与上面的类的代码相同这里不再重复
    bool existsEdge(int i,int j) const
    {
        if(i<1||j<1||i>n||j>n||aList[i].indexOf(j)==-1)
            return false;
        else
            return true;
    }
    void insertEdge(edge<bool>* theEdge)
    {
    int v1=theEdge->vertex1();
    int v2=theEdge->vertex2();
    if(v1<1||v2<1||v1>n||v2>n||v1==v2)
    {
        ostringstream s;
        s<<"("<<v1<<","<<v2<<") is not a permissible edge";
        throw illegalParameterValue(s.str());
    }
    if(aList[v1].indexOf(v2)==-1)
    {//新边
     aList[v1].insert(0,v2);
     e++;
    }
    }
    void eraseEdge(int i,int j)
    {
        if(i>=1&&j>=1&&i<=n&&j<=n)
        {
            int *v=aList[i].eraseElement(j);//删除,存在也删,不存在也删,不同之处在于,前者边数要减1
            if(v!=NULL)//边(i,j)存在
            e--;
        }
    }
    void checkVertex(int theVertex)const
    {
    if(theVertex<1||theVertex>n)
    {
    ostringstream s;
    s<<" no vertex"<<theVertex;
    throw illegalParameterValue(s.str());
    }
    }
    int degree(int theVertex)const
    {
        throw undefinedMethod("degree() undefined");
    }
    int outDegree(int theVertex)const
    {
        checkVertex(theVertex);
        return aList[theVertex].size();//该链表的大小就是其所有临界点的个数也就是出度
    }
    int inDegree(int theVertex)const
    {
        checkVertex(theVertex);
        //计算顶点theVetex的入边
        int sum=0;
        for(int j=1;j<=n;j++)//入度考虑的列和。所以要考虑每一行的数据
            if(aList[j].indexOf(theVertex)!=-1)
                sum++;
        return sum;
    }
    //迭代器代码省略
}

上面是用链表实现的一个加权有向图,其实很多代码和之前的类完全一样,唯一的区别就是链表实现中行用链表表示,一个链表的所有数据都是头节点的邻接点。
还有就是上述代码没有实现graphChain的代码,但是我们却默认使用了。

广度优先搜索:从一个顶点开始,搜索所有可到达顶点的方法。

virtual void bfs(int v,int reach[],int label)
{//广度优先搜索。reach[i]用来标记从v可到达的所有顶点
arrayQueue<int>q(10);
reach[v]=label;
q.push(v);
while(!q.empty())
{//从队列中删除一个标记过的顶点
int w=q.front();
q.pop();
//标记所有没有到达的的邻接于顶点w的顶点
vertexIterator<T>*iw =iterator(w);
int u;
while((u=iw.next())!=0)
    if(reach[u]==0)
    {
        q.push(u);
        reach[u]=label;
    }
    delete iw;
}
}

上述代码输入为起始顶点,可到达的顶点容器以及已到达顶点的标记。
首先创建一个队列将要查找邻接顶点的起始点v压入其中,然后从队列q中每次提取一个数据并删除然后查找该数据的邻接顶点,然后将找到的邻接顶点压入reach数组中并标记为label,再然后将这些顶点压入队列q,然后再提取q中的数据并删除,寻找该数据的邻接顶点,存入reach并标记为label(如果之前没有存入的话),并将那些没有到达过的点压入队列q,如此循环直至q为空,最后reach里面就是我们需要的v能到到的所有点的集合。
上面这个代码我们之前哪里好像使用过类似的。
上面的代码为使用迭代器生成的一个统一的代码,它适用于不同形式的图的实现。
下面分别为二维数组和链表实现图写专门的广度搜索代码:

二维数组的:

void bfs(int v,int reach[],int label)
{//广度优先搜索。reach[i]用来标记从v可到达的所有顶点
arrayQueue<int>q(10);
reach[v]=label;
q.push(v);
while(!q.empty())
{//从队列中删除一个标记过的顶点
int w=q.front();
q.pop();
//标记所有没有到达的的邻接于顶点w的顶点
for(int u=1;u<=n;u++)
{//访问顶点w的一个关联的顶点
if(a[w][u]!=noEdge&&reach[u]==0)
{//u是一个没有到达的顶点
q.push(u);
reach[u]=label;//做到达标记
}

}
}
}

与通用代码大同小异,只是没用迭代器。

链表描述的:

void bfs(int v,int reach[],int label)
{//广度优先搜索。reach[i]用来标记从v可到达的所有顶点
arrayQueue<int>q(10);
reach[v]=label;
q.push(v);
while(!q.empty())
{//从队列中删除一个标记过的顶点
int w=q.front();
q.pop();
//标记所有没有到达的的邻接于顶点w的顶点
for(chainNode<int>*u=aList[w].firstNode;u!=NULL;u->next)
{//访问顶点w的一个关联的顶点
    if(reach[u->element]==0)
    {//u->element是一个没有到达的顶点
     q.push(u->element);
     reach[u->elememt]=label;//做到达标记。

    }
}
}
}

通用的代码虽然能够适用于所有的图的描述方法,但是在性能上要差于定制的方法,所以要根据不同的情况使用不同的方法。

深度优先搜索:从起始点v开始寻找所有可能到达的顶点。(与广度搜索的目的一样,但是方法不一样)
这里是优先搜索当前点的一个邻接点然后更新当前点(而不会一次搜索当前点的所有邻接点)当当前点没有邻接点时就回退一步。
这种方法我们在迷宫代码那里使用过一次,只不过这里使用的是迭代的方法。

深度优先搜索的通用实现:

void rDfs(int v)
{//深度优先搜索递归方法
reach[v]=label;
vertexIterator<T>*iv=iterator(v);//从v开始迭代
int u;
while((u=iv->next())!=0)//只要v还存在邻接顶点就一直循环
    //访问与v相邻的顶点
if(reach[u]==0)//u是一个没有到达的顶点
rDfs(u);//开始迭代
delete iv;  
}
void dfs(int v,int reach[],int label)
{//深度优先搜索。reach[i]用来标记所有邻接于顶点v的可到达的顶点
graph<T>::reach=reach;
graph<T>::label=label;
rDfs(v);
}

每一次递归都会将一个与当前点相邻的点进行标记,直到当前点没有邻接点,然后会回退一步去寻找另一个相邻点的相邻点,直到回退到最初的循环层,并且循环结束。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值