首页 > 其他分享 >数据结构(第六章)

数据结构(第六章)

时间:2023-07-05 22:14:20浏览次数:33  
标签:结点 int 连通 ++ 邻接 第六章 顶点 数据结构

数据结构(第六章)

  • 定义:图是由顶点的有穷非空集合和顶点之间边的集合组成的,通常表示为G(V,E),其中,G表示一个图,V是图G中顶点的集合,E是图G中边的集合。
  • 特性:
    1. ​ 在图中数据元素,我们称之为顶点。
    2. 任意两个顶点之间都有可能有关系,顶点之间的逻辑关系用边来表示。

无向图

定义:如果图中任意两个顶点之间的边都是无向边,则称该图为无向图。

无向边:若顶点Vi到Vj之间的边没有方向,则称这条边为无向边,用无序偶对(Vi,Vj)来表示。

有向图

若E是有向边(也称弧)的有限集合时,则图G为有向图。弧是顶点的有序对,记为<v,w>,其中v,是顶点,v称为弧尾,w称为弧头,<v,w>称为v到w的弧,也称v邻接到w

图的其他特性

  1. 简单图

一个图G如果满足:1.不存在重复边 2.不存在顶点到自身的边 ,那么称图G为简单图。

  1. 完全图

对于无向图,|E|的取值范围为0到n(n-1)/2,有n(n-1)/2条边的无向图称为完全图,在完全图中任意两个顶点之间都存在边。对于有向图,|E|的取值范围为0到n(n-1),有n(n-1)条弧的有向图称为有向完全图,在有向完全图中任意两个顶点之间都存在方向相反的两条弧。

  1. 连通图、连通分量

图G中任意两个顶点都是连通的,则称图G为连通图。无向图中的极大连通子图称为连通分量。

  1. 强连通图、强连通分量

在有向图中,如果一对顶点v和w,从v到w和从w到v之间都有路径,则称这两个顶点是强连通的。若图中任何一对顶点都是强连通的,则称此图为强连通图。有向图中的极大强连通子图称为有向图的强连通分量。

注意:极大连通子图是无向图的连通分量,极大即要求该连通子图包含其所有的边;极小连通子图是既要保持图连通又要使得边数最少的子图。

  1. 生成树、生成森林

连通图的生成树是包含图中全部项点的一个极小连通子图。若图中顶点数为n,则它的生成树含有n-1条边。包含图中全部顶点的极小连通子图,只有生成树满足这个极小条件,对生成而言。若砍去它的一条边,则会变成非连通图,着加上一条边则会形成一个回路。在非连通图中,连通分量的生成树构成了非连通图的生成森林。

  1. 顶点的度、入度和出度

在无向图中,顶点v的度是指依附于顶点v的边的条数,记为TD(v)。对于具有n个顶点-e条边的无向图,∑TD(v)=2e,即无向图的全部顶点的度的和等于边数的2倍,因为每条边和两个项点相关联。在有向图中,顶点v的度分为入度和出度,入度是以顶点v为终点的有向边的数目,记为I(v);而出度是以顶点v为起点的有向边的数目,记为OD(v)。顶点v的度等于其入度与出度之和,即TD(V)=ID(v)+OD(v)。对于具有n个顶点、e条边的有向图,∑ID(v)-∑OD(v)=e,即有向图的全部顶点的入度之和与出度之和相等,并且等于边数,这是因为每条有向边都有一个起点和终点。

  1. 边的权和网

在一个图中,每条边都可以标上具有某种含义的数值,该数值称为该边的权值。这种边上带有权值的图称为带权图,也称网。

  1. 稠密图和稀疏图

边数很少的图称为稀疏图,反之称为稠密图。稀疏和稠密本身是模糊的概念,稀疏图和稠密图常常是相对而言的。一般当图G满足|E|<|V|log|V|时,可以将G视为稀疏图。

  1. 路径、路径长度和回路

路径上边的数目称为路径长度。第一个顶点和最后一个顶点相同的路径称为回路或环。若一个图有n个顶点,并且有大于n-1条边,则此图一定有环。

  1. 简单路径、简单回路

在路径序列中,顶点不重复出现的路径称为简单单路径。除第一个顶点和最后一个顶点外,其余顶点不重复出现的回路称为简单回路。

  1. 距离

从顶点u出发到顶点v的最短路径若存在,则此路径的长度称为从u到v的距离。若从u到v根本不存在路径,则记该距离为无穷。

  1. 有向树

一个顶点的入度为0、其余顶点的入度均为1的有向图,称为有向树。

图的存储及基本操作

一、邻接矩阵表示法(重点)
  • 主要适用于稠密网

  • 定义:邻接矩阵存储,是指用一个一维数组存储图中顶点的信息,用一个二维数组存储图中边的信息(即个顶点间的邻接关系),存储顶点之间邻接关系的二维数组称为邻接矩阵。

邻接矩阵的存储结构:

#define MAXSIZE  //最大顶点数
#define INFINITY 65535 //代表无穷
typedef int VertexType; //顶点类型
typedef int EdgeType ; //边的权值大小
typedef struct {
    VertexType vexs[MAXSIZE] ; //顶点个数
    EdgeType arc[MAXSIZE][MAXSIZE] ; //邻接矩阵 ,边的个数
    int numvexs , numarcs; //图中当前顶点数和边数
}MGraph;

创建邻接矩阵:

void CreateMGraph(MGraph &G){
    int k ,w ;  //邻接矩阵的坐标点
    EdgeType e; //边的权值大小
    cout<<"输入顶点个数和边数:\n";
    cin>>G.numvexs>>G.numarcs;
    for (int i = 0; i < G.numvexs; ++i) { //输入顶点值,创建顶点表
       cin>> G.vexs[i];
    }
    for (int i = 0; i < G.numvexs; ++i) { //邻接矩阵初始化
        for (int j = 0; j < G.numvexs; ++j) {
            G.arc[i][j]=INFINITY;
        }
    }
    for (int i = 0; i < G.numarcs; ++i) {
        G.arc[k][w]=e;
        G.arc[w][k]=G.arc[k][w];//无向图带权值的邻接矩阵创建方法
    }
}
二、邻接表表示法(重点)
  • 主要适用于存储稀疏图
  • 所谓邻接表,是指对图G中的每个顶点v建立一个单链表,第i个单链表中的结点表示依附于顶点v的边(对于有向图则是以顶点vi为尾的弧),这个单链表就称为顶点v的边表(对于有向图则称为出边表)。边表的头指针和顶点的数据信息采用顺序存储(称为顶点表),所以在邻接表中存在两种结点:顶点表结点和边表结点。顶点表结点由顶点域(data)和指向第一条邻接边的指针(firstarc)构成,边表(邻接表)结点由邻接点域(adjvex)和指向下一条邻接边的指针域(nextarc)构成。

表的结构:

顶点域 边表头指针
data firstarc
邻接点域 指针域
adjvex nextarc
  • 特点:邻接表不唯一。若无向图中有n个顶点,e条边,则其邻接表需要n个头结点和2e个表结点。

邻接表的存储结构:

#define MaxVertexNum 100 //图中顶点数目的最大值
typedef int VertexType; //顶点类型
typedef  int EdgeType; //边上的权值类型
typedef struct ArcNode{ //边表结点
    int adjvex;  //该弧所指向顶点的位置
    struct ArcNode *next; //指向下一条弧的指针
}ArcNode;
typedef struct VNode{  //顶点表结点
    VertexType data; //顶点信息
    ArcNode *first; //指向第一条依附在该顶点的弧的指针
}VNode , AdjList[MaxVertexNum];
typedef struct {
    AdjList vertices; //邻接表
    int vexnums ,arcnums; //图的顶点和弧数
}ALGraph; //邻接表结构

创建无向图的邻接表:

//创建无向图的邻接表
void CreateALGraph(ALGraph &A){
    int k ,w ;//定义边上的顶点的序号(k,w)
    ArcNode *a;
    cout<<"输入顶点数和边数:"<<endl;
    cin>>A.vexnums>>A.arcnums;
    for (int i = 0; i < A.arcnums; ++i) { //读入顶点信息,创建空表
        cin>>A.vertices[i].data;//输入顶点信息
        A.vertices[i].first=NULL; //将边表置为空表
    }
    //这里主要用到了头插法的思想
    for (int i = 0; i < A.arcnums; ++i) { //创建边表
        cout<<"输入边表上的顶点序号:"<<endl;
        cin>>k>>w;
        a=new ArcNode ; //开辟一个结点空间
        a->adjvex=k; //邻接序号
        a->next=A.vertices[w].first; //表示将a的指针指向当前顶点指向的结点
        A.vertices[w].first=a;//将当前顶点的指针指向e
        a=new ArcNode ;
        a->adjvex=w;//邻接序号
        a->next=A.vertices[k].first;//表示将a的指针指向当前顶点指向的结点
        A.vertices[k].first=a;//将当前顶点的指针指向e
    }
}
三、十字链表法
  • 主要适用于有向图
  • 十字链表是有向图的一种链式存储结构。在十字链表中,对应有向图中的每一条弧有一个结点,对应于每个顶点也有一个结点。

表的结构:

弧结点:

tailvex headvex hlink tlink info

顶点结点:

data firstin firstout
四、邻接多重表
  • 主要适用于无向图
  • 在邻接多重表中,所有依附于同一顶点的边串联在同一链表中,由于每条边依附于两个顶点,因此每个边结点同时链接在两个链表中。对无向图而言,其邻接多重表和邻接表的差别仅在于,同一条边在邻接表中用两个结点表示,而在邻接多重表中只有一个结点。

表的结构:

边结点:

mark ivex ilink jvex jlink info

顶点结点:

data firstedge

图的遍历

一、深度优先搜索(DFS)
  • 特点:按照路径依次向更深层次访问
  • 算法性能分析:DFS算法是一个递归算法,需要借助一个递归工作栈,故其空间复杂度为0(|V|).
    遍历图的过程实质上是对每个顶点查找其邻接点的过程,其耗费的时间取决于所用的存储结构。以邻接矩阵表示时,查找每个顶点的邻接点所需的时间为O(|V|),故总的时间复杂度为0(|V|^2).以邻接表表示时,查找所有顶点的邻接点所需的时间为0(|E|),访问顶点所需的时间为0(|V|).此时,总的时间复杂度为0(|V|)+(|E|)。

邻接矩阵的深度优先递归算法

bool visited[MAXSize]; //代表该顶点是否被访问
//邻接矩阵的深度优先递归算法
/*思想:一直往里面走,类似于树的先序遍历算法,只有当此路径上的所有顶点都被访问,才退回到上一个顶点
 * */
void DFS(MGraph G,int i){ // i表示起始的顶点位置
    visited[i]= true;
    cout<<"输出遍历的顶点值为:"<<G.vexs[i];
    for (int j = 0; j < G.numvexs; ++j) {
        if (G.arc[i][j]==1&&!visited[i]) //判断路径是否存在且并未被访问过
            DFS(G,j);  //递归的遍历
    }
}
//邻接矩阵的深度遍历操作
void DFSTraverse(MGraph G){
    for (int i = 0; i < G.numvexs; ++i) {
        visited[i]= false;
    }
    for (int i = 0; i < G.numvexs; ++i) {
        if (!visited[i]) //当存在非连通图时判断,所有顶点是否都已被访问
         DFS(G,i);
    }
}

邻接表的深度优先递归算法

bool visited[MaxVertexNum]; //访问的标志数组
//邻接表的深度优先递归算法
void DFS(ALGraph A ,int i){
    ArcNode *p ;//边表结点
    visited[i]=true; //标识该结点被访问
    cout<<"打印遍历到的顶点"<<A.vertices[i].data;
    p=A.vertices[i].first; //起始邻接表的遍历
    while(p){
        if (!visited[p->adjvex])
            DFS(A,p->adjvex);//对未访问的邻接顶点递归调用
        p=p->next;
    }
}
void DFSTraverse(ALGraph A){
    for (int i = 0; i < A.vexnums; ++i) {
        visited[i]= false;  //初始所有顶点都是未被访问的状态
    }
    for (int i = 0; i < A.vexnums; ++i) {
        if (!visited[i])  //对未访问的顶点调用DFS,若是连通图,只会执行一次
            DFS(A,i);
    }
}
二、广度优先搜索(BFS)
  • 特点:依次访问邻接点
  • 算法性能分析:无论是邻接表还是邻接矩阵的存储方式,BFS 算法都需要借助一个辅助队列Q,n个顶点均需入队一次,在最坏的情况下,空间复杂度为0(|V|)。采用邻接表存储方式时,每个顶点均需搜索一次(或入队一次),故时间复杂度为0(|V|),在搜索任一顶点的邻接点时,每条边至少访问一次,故时间复杂度为0(|E|),算法总的时间复杂度为0(|V|+|E|),采用邻接矩阵存储方式时,查找每个顶点的邻接点所需的时间为0(|V|),故算法意的时间复杂度为0(|V|^2)。

邻接矩阵的广度优先搜索

//邻接矩阵的广度优先算法
//其思想类似于图的层序遍历
bool visited[MAXSIZE]; //访问标记数组
void BFS(MGraph G ,int v){ //从顶点v出发,广度优先遍历图G
    visit(v);//访问初始顶点v
    visited[v]= false;//标记访问过的结点
    EnQueue(Q,v); //顶点v入队
    while(!IsEmpty(Q)){
        DeQueue(Q,v); //将队首元素出队
        for (int i = 0; i < G.numvexs; ++i) {  //判断其他顶点是否与当前顶点存在边且未被访问
            if (G.arc[v][i]==1&&!visited[i]){
                visited[i]=true; //找到顶点,标记为已访问
                cout<<"打印遍历到的顶点:"<<G.vexs[i];//visit(v)
                EnQueue(Q,i);//将找到的此顶点入队
            }
        }
    }
}
void BFSTraverse(MGraph G){ //对图进行广度优先遍历
    for (int i = 0; i < G.numvexs; ++i) { //访问标记数组初始化
        visited[i]= false;
    }
    InitQueue(Q); //初始化队列
    for (int i = 0; i < G.numvexs; ++i) { //从0号结点开始遍历
        if (!visited[i]) //对每个连通分量调用一次BFS
            BFS(G,i);
    }
}

邻接表的广度优先搜索

//邻接表的广度优先遍历搜索
bool visited[MaxVertexNum]; //访问标记数组
void BFS(ALGraph A , int v){
        ArcNode *p; //定义边表结点
        visit(v);
        visited[v]= true;
        EnQueue(Q,v);
        while(!IsEmpty(Q)){
            DeQueue(Q,v);
            p=A.vertices[v].first;//找到当前顶点的边表链的表头指针
            while(p){
                if (!visited[p->adjvex]) //此顶点未被访问
                {
                    visited[p->adjvex]=true;
                    cout<<A.vertices[p->adjvex].data;//visit(p->adjvex)访问该顶点
                    Enqueue(Q,p->adjvex);
                }
                p=p->next; //指向下一个邻接点
            }
        }
}
void BFSTraverse(ALGraph A){ //对图A进行广度优先遍历
    for (int i = 0; i < A.vexnums; ++i) { //访问标记数组初始化
        visited[i]= false; 
    }
    for (int i = 0; i < A.vexnums; ++i) {
        if (!visited[i]) //每个连通分量调用一次BFS
            BFS(A,i);
    }
}

最小生成树(MST)

一、Prim算法
  • 主要用于稠密图

  • 从所有的顶点中,任选一点为起点先将该顶点加入表中,比较与之连接的边中权值最小的一条与之连接,将连接之后的顶点也加入到表中,再次比较与之相连接的边,选出其中权值最小的边,依此类推。

Prim算法:

void Prim(MGraph G){
    int min , i ,j ,k;
    int adjvex[MAXVEX]; //用于保存相关顶点间边的权值点的下标
    int lowcost[MAXVEX];//用于保存相关顶点间的权值
    lowcost[0]=0; //初始化第一个的权值为0,即将第一个顶点V0加入最小生成树
    adjvex[0]=0;//初始化第一个顶点V0的下标为0
    for ( i = 1; i <G.vexnums; ++i) {
        lowcost[i]=G.arc[0][i];//将与第一个顶点V0有关的所有边的权值都存入到数组中
        adjvex[i]=0;//初始化都为V0的下标
    }
    for ( i = 1; i < G.vexnums; ++i) {
        min=INF;//初始化最小值为无穷
        j=1;k=0;
        while(j<G.vexnums){ //循环全部的顶点
            if (lowcost[j]!=0&&lowcost[j]<min){ //如果权值不为0并且权值小于最小值,则让当前权值为最小值
                min=lowcost[j];
                k=j; //将当前最小值的权值存入k
            }
            j++;
        }
        cout<<"当前顶点边中权值最小的边: ("<<adjvex[k]<<" "<<k<<")\n";
        lowcost[k]=0; //将当前顶点权值设置为0,表示此点任务完成,加入到了最小生成树当中
        for ( j = 1; j <G.vexnums ; ++j) { 
            //更新lowcost表,将与下标为k的顶点相连的边的权值,与lowcost表中进行比较,更新其中权值更大的
            if (lowcost[j]!=0&&lowcost[j]>G.arc[k][j]){
                lowcost[j]=G.arc[k][j]; //将较小的权值存入lowcost相应的位置
                adjvex[j]=k; //将下标为k的顶点存入表中
            }
        }
    }
}
二、Kruskal算法
  • 主要用于稀疏图

  • 从所有边中选择权值最小的边,使这条边的两头连通(原本就连通的就不需要选),直到所有的顶点都连通。

Kruskal算法:

#define MAXEDGE 100 //定义边的数量的极大值
typedef struct {
    int begin;
    int end;
    int weight;
}Edge;
//查找连接顶点的尾部下标,即为0的根结点
int Find(int *parent , int f){
      while(parent[f]>0){
          f=parent[f];
      }
      return f;
}
//Krusual算法
void Krusual(MGraph G){
    Edge edges[MAXEDGE]; //定义边集数组
    int parent[MAXVEX]; //定义数组来判断边与边是否形成环路
    //将邻接矩阵转换为边集数组edges并按权由大到小排序
    for (int i = 0; i < G.vexnums; ++i) {
        for (int j =i+1; j < G.vexnums; ++j) {
            if (G.arc[i][j]!=0&&G.arc[i][j]!=INF){
                edges[i].begin=i;
                edges[i].end=j;
                edges[i].weight=G.arc[i][j];
            }
        }
    }
    //进行排序,从小到大的顺序
    for (int i = 0; i < G.vexnums; ++i) {
        for (int j = i+1; j <G.vexnums ; ++j) {
            if (edges[i].weight>edges[j].weight){
                int temp;
                temp=edges[j].weight;
                edges[j].weight=edges[i].weight;
                edges[i].weight=temp;
            }
        }
    }
    for (int i = 0; i < G.vexnums; ++i) { //初始化数组值为0
        parent[i]=0;
    }
    for (int i = 0; i < G.arcnums; ++i) { //循环每一条边
        int n,m;
        n= Find(parent,edges[i].begin);
        m= Find(parent,edges[i].end);
        if (n!=m){ //当n与m不等时,说明这条边没有与现有生成树生成环路
            parent[n]=m;//将此边的结尾顶点放入下标为起点的parent中,表示此顶点已经在生成树集合中。
            cout<<edges[i].begin<<edges[i].end<<edges[i].weight<<endl;
        }
    }
}

最短路径

一、Dijkstra算法
  • 按照路径长度递增的次序产生最短路径的算法。
  • 时间复杂度为:0(n^2)
  • 主要用于求单源最短路径问题

Dijkstra算法:

typedef int Path[MAXVEX] ; //用于存储最短路径的前驱
typedef int Distance[MAXVEX]; //用于存储各点最短路径的权值和
//Dijkstra算法,求有向网G的V0顶点到其余顶点V的最短路径P[V]及带权长度
//P[V]的值为前驱结点的下标,D[V]表示V0到V的最短路径长度和
void Dijkstra(MGraph G ,int V0 ,Path *P,Distance *D){
    int final[MAXVEX]; //标记该结点是否已经访问
    for (int i = 0; i < G.numvexs; ++i) {
        final[i]=0;  //全部顶点初始化为0
        (*D)[i]=G.arc[V0][i]; //该邻接矩阵中的权值
        (*P) [i]=V0; //初始化前驱结点
    }
    final[V0]=1; //表示该顶点被访问,标记
    //开始主循环,每次求得V0到某个顶点V的最短路径(权值最小)
     int min=INFINITY; //初始化最小值为INF,用于表示所知距离V0顶点的最近距离(权值最小的边)
     int k ; //用于更新标记点
    for (int i = 0; i <G.numvexs; ++i) {
        for (int j = 0; j < G.numvexs; ++j) { //寻找距离V0最近的顶点
            if (!final[j]&&(*D)[i]<min){
                k=j; //获取最小权值的下标
                min=(*D)[j];//将最短路径赋值给min
            }
        }
        final[k]=1; //标记该顶点被访问,此时开始从此 顶点寻找最短路径
        for (int j = 0; j < G.numvexs; ++j) { //更新权值表Distance和路径前驱Path
            if (!final[j]&&min+G.arc[k][j]<(*D)[j]) //如果经过V顶点的路径比现在这条路径段则进行更新
            {
                (*D)[j]=min+G.arc[k][j];//更新Distance表
                (*P)[j]=k; //更新前驱顶点
            }

        }
    }
}
//打印路径 从后往前需要栈
void Print(int path[],int j)
{
int stc[MAXVEX];
int top=-1;
while(path[j]!=-1)//有路径
{
stc[++top]=j;
j=path[j];
}
stc[++top]=j;
while(top!=-1)
{
cout<<stc[top--]<<' ';
}
cout<<endl;
}

二、Floyd算法
  • 允许图中有带负权值的边,但不允许有包含带负权值的边组成的回路。
  • 适用于带权无向图
  • 时间复杂度为:0(n^2)
  • 主要用于求各顶点之间最短路径问题

Floyd算法:

typedef int Path[MAXVEX][MAXVEX]; //前驱
typedef int Distance[MAXVEX][MAXVEX]; //带权路径长度
//Floyd算法,求网图G各顶点到其余顶点w的最短路径P[v][w]及带权长度D[v][w]
void Floyd(MGraph G ,Path *P ,Distance *D){
    for (int v = 0; v < G.numvexs; ++v) { //初始化D和P
        for (int w = 0; w < G.numvexs; ++w) {
            (*D)[v][w]=G.arc[v][w]; //D[v][w]的值为对应边上的权值
            (*P)[v][w]=-1; //初始化P
        }
    }
    for (int k = 0; k < G.numvexs; ++k) { //考虑已K为中转点
        for (int v = 0; v <G.numvexs; ++v) { //v表示行号
            for (int w = 0; w < G.numvexs; ++w) { //w表是列号
                if ((*D)[v][w]>(*D)[v][k]+(*D)[k][w]){ //如果经过下标k的路径比原来的路径短
                    (*D)[v][w]=(*D)[v][k]+(*D)[k][w];//更新最短路径
                    (*P)[v][w]=k;//更新中转点
                }
            }
        }
    }
}
void Print(int u,int v,int path[][],int Distance[][])//运用递归与中间结点输出路径
{
if(Distance[u][v]==INF)//无路径直接返回
return;
else if(path[u][v]==-1)//无中间结点直接输出
cout<<u<<' '<<v;
else//运用递归与中间结点输出路径
{
int mid=path[u][v];
Print(u,mid,path,A);
print(mid,v,path,A);
}
}

拓扑排序

  • AOV网:若用DAG图表示一个工程,其顶点表示活动,用有向边<Vi,Vj>表示活动Vi必须先于活动Vj进行的这样一种关系,则将这种有向图称为顶点表示活动网络,记为AOV网。

  • 拓扑排序:在图论中,由一个有向无环图的顶点组成的序列,当且仅当满足下列条件时,称为该图的一个拓扑排序:
    ①每个顶点出现且只出现一次。
    ②若顶点A在序列中排在顶点B的前面,则在图中不存在从顶点B到顶点A的路径。或定义为:拓扑排序是对有向无环图的顶点的一种排序,它使得若存在一条从顶点A到顶点B的路径,则在排序中顶点B出现在顶点A的后面。每个AOV网都有一个或多个拓扑排序序列。

  • 对一个AOV网进行拓扑排序的算法有很多,下面介绍比较常用的一种方法的步骤:

    ①从AOV网中选择一个没有前驱的顶点并输出。
    ②从网中删除该顶点和所有以它为起点的有向边。
    ③重复①和②直到当前的AOV网为空或当前网中不存在无前驱的顶点为止。后一种情况说
    明有向图中必然存在环。

拓扑排序:

#define MAXVEX 100
//采用邻接表的结构进行存储
typedef struct ArcNode{ //边表结点
    int adjvex;  //邻接点域,存储该顶点对应的下标
    int weight;  //用于存储权值,对于非网图可以不需要
    struct ArcNode *next; //链域,用于指向下一个邻接点
}ArcNode;
typedef struct VertexNode{ //顶点表结点
    int in; //顶点的入度
    int data; //顶点域,存储顶点信息
    ArcNode *firstedge; //边表头指针
}VertexNode ,AdjList[MAXVEX];
typedef struct {
    AdjList vertices;
    int numvexs, numarc; //图中当前顶点数和边数
}ALGraph;

//拓扑排序,若G无回路,则输出拓扑排序序列并返回1,若有回路则返回0
int TopologicalSort(ALGraph *G){
 
        int n=0;
        int stc[MAXVEX],top=-1; //栈指针的下标
        for(int i=0;i<G->numvexs;i++)
        {
            if(G->vertices[i].in==0) //将入度为0的点入栈
            {
                stc[++top]=i;
            }
        }
        ArcNode *p; //定义边表结点
        while(top!=-1)
        {
            int k=stc[top--]; //将所有与入度为0的顶点的度减一
            visit(k); //访问此顶点
            n++; //统计顶点数
            p=G->vertices[k].firstedge;
            while(p!=NULL) //对顶点弧表进行遍历
            {
                int j=p->adjvex;
                --G->vertices[j].in; //将该顶点的入度减一
                if(G->vertices[j].in==0) //若为0则出栈,以便后续的visit操作
                {
                    stc[++top]=j;
                }
                p=p->next;
            }
        }
        if(n==G->numvexs) //n小于顶点数说明存在环
        {
            return 1;
        }
        else
            return 0;
    }

标签:结点,int,连通,++,邻接,第六章,顶点,数据结构
From: https://www.cnblogs.com/wfy-studying/p/17529924.html

相关文章

  • 数据结构--单向链表
    如果对于顺序表的结构已经大致了解,那么对单向链表的学习就会轻松一些。顺序存储中的数据因为挤在一起而导致需要成片移动,那很容易想到的解决方案是将数据离散地存储在不同内存块中,然后在用来指针将它们串起来。这种朴素的思路所形成的链式线性表,就是所谓的链表。顺序表和链表在内存......
  • 数据结构与算法coding过程中的记录
     1.init()时一定要记得malloc()申请新的内存空间(如果不申请内存空间程序返回的值是有内存里的脏数据,把人看得云里雾里找不到问题出在哪)2.带头结点单链表尾插法要注意:若LNode*p=L->next;循环条件是while(p!=NULL){p=p->next;},那么最后的p是NULL,此时在p(NULL)后插一个结点......
  • 《数据结构与算法》之图
    导言:图是数据结构教材上的最后一种数据结构了,它的使用范围最广,最多,也是最贴合我们实际生活的,图是一个多对多的数据结构,在前面的学习,了解到了一对一的数据结构----线性结构,以及一对多的结构----树形结构,现在要学的多对多的结构----图,图是对我们现实生活中很多实体的抽象,因为实际......
  • Redis九种数据结构
    深度剖析Redis九种数据结构实现原理,建议收藏 1.Redis介绍Redis是一个高性能的键值存储系统,支持多种数据结构。包含五种基本类型String(字符串)、Hash(哈希)、List(列表)、Set(集合)、Zset(有序集合),和三种特殊类型Geo(地理位置)、HyperLogLog(基数统计)、Bitmaps(位图)。每种数据结构......
  • 数据结构与算法(一): 稀疏数组
    问题引入在五子棋游戏或类似的游戏中,我们可以把整个棋盘想象成是一个有规律的二维数组,其值由0、1、2三个数字组成,0代表空白区域,1代表白子,2代表黑子。这种情况:即当一个数组中大部分元素为0或者为同一值时,存储该数组数据可以使用稀疏数组来对原始数组进行精简,以减少原始数组中无用......
  • 【算法】基础数据结构
    一、单调栈1.概念满足单调性的栈结构,常用于RMQ问题。2.实现为满足单调性,我们在栈的基础上额外判断以下栈顶元素是否大于/小于当前元素。以下面的序列\(1\;7\;4\;3\;2\;8\)为例,需要求每一个数右边第一个比它大的数。考虑维护单调递减栈,才能保证不会有数没有找到答案便被......
  • 数据结构课后题答案 - XDU_953
    参考书:数据结构与算法分析(第二版)作者:荣政编出版社:西安电子科技大学出版社出版日期:2021年01月01日答案解析: ......
  • JavaScript 算法和数据结构之——基础JavaScript 笔记
    做整理是为了知识更加系统一些,遂记录参考资料js基础算法JavaScript字符串可以用单引号或双引号查找字符串长度.length空格符也会计算在内使用方括号查找字符串中的第一个字符方括号表示法(Bracketnotation)是一种在字符串中的特定index(索引)处获取字符的方法xxx[0]获取......
  • C++ 数据结构
    C++数据结构C/C++数组允许定义可存储相同类型数据项的变量,但是结构是C++中另一种用户自定义的可用的数据类型,它允许您存储不同类型的数据项。结构用于表示一条记录,假设您想要跟踪图书馆中书本的动态,您可能需要跟踪每本书的下列属性:Title:标题Author:作者Subject:类目......
  • 开心档之C++ 数据结构
    C++数据结构C/C++数组允许定义可存储相同类型数据项的变量,但是结构是C++中另一种用户自定义的可用的数据类型,它允许您存储不同类型的数据项。结构用于表示一条记录,假设您想要跟踪图书馆中书本的动态,您可能需要跟踪每本书的下列属性:Title:标题Author:作者Subject:类目Bo......