DS博客作业04--图

这个作业属于哪个班级 数据结构--网络2011/2012
这个作业的地址 DS博客作业04--图
这个作业的目标 学习图结构设计及相关算法
姓名 王历

0.PTA得分截图

1.本周学习总结(6分)

1.1 图的存储结构

图的存储主要分两种,一种邻接矩阵,另一种邻接表
用不同的方式存储图的信息

1.1.1 邻接矩阵

  • 邻接矩阵的结构体定义
#define MAXV<最大顶点数>
typedef struct {
	int no;//顶点编号
	INfoType info;//顶点其他信息
}VertcxRype;
typedef struct {
	int edges[MAXV][MAXV];//邻接矩阵
	int n, e;//顶点数,边数
	VertcxRype vexs[MAXV];//存放顶点信息
}MatGraph;
  • 建图函数
void CreateMGraph(MGraph &g, int n, int e)//建图 
{
	//n顶点,e弧数
	g.n = n;
	g.e = e;
	int i, j;
	int a, b;//下标
	for (i = 1; i <= n; i++)//先进行初始化
	{
		for (j = 1; j <= n; j++)
		{
			g.edges[i][j] = 0;
		}
	}
	for (i = 1; i <= e; i++)//无向图
	{
		cin >> a >> b;
		g.edges[a][b] = 1;
		g.edges[b][a] = 1;
	}
}

1.1.2 邻接表

  • 邻接表的结构体定义
typedef struct ANode
{  int adjvex;            //该边的终点编号
   struct ANode *nextarc;    //指向下一条边的指针
   int info;    //该边的相关信息,如权重
} ArcNode;                //边表节点类型
typedef int Vertex;
typedef struct Vnode
{  Vertex data;            //顶点信息
   ArcNode *firstarc;        //指向第一条边
} VNode;                //邻接表头节点类型
typedef VNode AdjList[MAXV];
typedef struct 
{  AdjList adjlist;        //邻接表
   int n,e;        //图中顶点数n和边数e
} AdjGraph;   
  • 建图函数
void CreateAdj(AdjGraph *&G, int n, int e)//创建图邻接表
{
	int i, j, a, b;
	G = new AdjGraph;
	for (i = 1; i <= n; i++)//邻接表头结点置零
	{
		G->adjlist[i].firstarc = NULL;
	}
	for (j = 1; j <= e; j++)//无向图
	{
		cin >> a >> b;	
		ArcNode *p,*q;
		p = new ArcNode;
		q = new ArcNode;
		p->adjvex = b;//用头插法进行插入
		q->adjvex = a;
		p->nextarc = G->adjlist[a].firstarc;
		G->adjlist[a].firstarc = p;
		q->nextarc = G->adjlist[b].firstarc;
		G->adjlist[b].firstarc = q;
	}
	G->n = n;
	G->e = e;
}

1.1.3 邻接矩阵和邻接表表示图的区别

  1. 邻接矩阵的空间复杂度为0(n2),而邻接表的空间复杂度为0(n+e)。
  2. 如果图中边的数目远远小于n2称作稀疏图,这时用邻接表表示比用邻接矩阵表示节省空间;
    如果图中边的数目接近于n2,对于无向图接近于n*(n-1)称作稠密图,考虑到邻接表中要附加链域,采用邻接矩阵表示法为宜。因此当数据的边关系较为复杂时用邻接矩阵,稀疏时用领接表,内存空间不会浪费。

1.2 图遍历

1.2.1 深度优先遍历

对上无向图进行深度优先遍历,从A开始:
第1步:访问A。

第2步:访问B(A的邻接点,由储存结构决定的)。

第3步:访问G(B的邻接点)。 和B相连只有"G"(A已经访问过了)

第4步:访问E(G的邻接点)。 在第3步访问了B的邻接点G之后,接下来应该访问G的邻接点,即"E和H"中一个(B已经被访问过,就不算在内)。而由于E在H之前,先访问E。

第5步:访问C(E的邻接点)。 和E相连只有"C"(G已经访问过了)。

第6步:访问D(C的邻接点)。

第7步:访问H。因为D没有未被访问的邻接点;因此,一直回溯到访问G的另一个邻接点H。

第8步:访问(H的邻接点)F。
因此访问顺序是:A -> B -> G -> E -> C -> D -> H -> F

  • 深度遍历代码
    邻接矩阵
void DFS(MGraph g, int v)//邻接矩阵深度遍历 
{
   
    if (flag == 0)
    {
        cout << v;
        flag = 1;
    }
    else
        cout << " " << v;   //输出顶点
    visited[v] = 1;//标记已访问该节点
    for (int i = 1; i <= g.n; i++)
    {
        if(g.edges[v][i] == 1 && visited[i] == 0)
        {
            DFS(g, i); //当前顶点与 i 顶点邻接且未被访问,递归搜索
        }
    }
}

邻接表

void DFS(AdjGraph *G, int v)//v节点开始深度遍历 
{
	visited[v] = 1;
	ArcNode *p;//新建结点储存当前信息
	if (flag == 0)
	{
		cout << v;
		flag = 1;
	}
	else
	{
		cout << " " << v;
	}
	p = G->adjlist[v].firstarc;
	while (p != NULL)//遍历当前链
	{
		if (visited[p->adjvex] == 0)//判断未访问过
		{
			DFS(G, p->adjvex);
		}
		p = p->nextarc;
	}
}
  • 深度遍历适用哪些问题的求解
    可以找到两点之间的全部路径,以此可以找到迷宫问题

1.2.2 广度优先遍历

  • 广度遍历代码
    邻接矩阵
void BFS(MGraph g, int v)//广度遍历 
{
    int front;
    queue<int>q;
    q.push(v);
    visited[v] = 1;
    cout << v;
    while (!q.empty())
    {
        front = q.front();
        q.pop();
        for (int i = 0; i < g.n; i++)
        {
            if (g.edges[front][i] == 1 && visited[i] == 0)
            {
                q.push(i);
                visited[i] = 1;
                cout << " " << i +;
            }
        }
    }
}

邻接表

void BFS(AdjGraph* G, int v) //v节点开始广度遍历  
{
    int i, j;
    int front;
    queue<int>q;
    ArcNode* p;
    q.push(v);
    visited[v] = 1;
    cout << v;
    while (!q.empty())
    {
        front = q.front();
        q.pop();
        p = G->adjlist[front].firstarc;
        do
        {
            if (p != NULL&&visited[p->adjvex]==0)
            {
                q.push(p->adjvex);
                visited[p->adjvex ] = 1;
                cout << " " << p->adjvex ;
            }
            p = p->nextarc;
        }while (p != NULL);
    }
}
  • 广度遍历适用哪些问题的求解。
    最短路径
    最远顶点
    最短单词路径等

1.3 最小生成树

有n个顶点的连通网可以建立不同的生成树,每一颗生成树都可以作为一个连通网,当构造这个连通网所花的权值最小时,搭建该连通网的生成树,就称为最小生成树。

1.3.1 Prim算法求最小生成树

如图所示prim来生成最小数:


  • 使用Prim算法要借助两个数组做工具,一个是lowcost[]//存放候选边,每个顶点到u中最小边。
    另一个是closet[]//U中顶点的邻边顶点

代码

#define INF 32767
void Peim(MGraph g, int v)
{
	int lowcost[MAXV];
	int min;
	int closest[MAXV];
	int i, j, k;
	for (i = 0; i < g.n; i++)
	{
		lowcost[i] = g.edges[v][i];//置初值,放入顶点v和所有顶带你的权值
		closest[i] = v;
	}
	for (i = 1; i < g.n; i++)//n-1条边,进行n-1次
	{
		min = INF;
		for (j = 0; j < g.n; j++)//遍历找到权值最小的
		{
			if (lowcost[j] != 0 && lowcost[j] < min)
			{
				min = lowcost[j];
				k = j;//记录下标
			}
		}
		lowcost[k] = 0;//lowcost为0表示该顶点已使用
		for (j = 0; i < g.n; j++)//遍历所有顶点,比较找到的顶点与其他顶点的权值是否比原来小
		{
			if (lowcsost[j] != 0 && g.edges[k][j] < lowcost[j])
			{
				lowcost[j] = g.edges[k][j];
				closest[j] = k;//改变权值和相邻的顶点
			}
		}
	}
}

时间复杂的为O(n的平方),其适用于边数较多的稠密图,其是通过比较边来找顶点,每次遍历找到一个顶点,
与顶点个数无关。适用于邻接矩阵,需要调用到权值,找到特定顶点间的权值。

1.3.2 Kruskal算法求解最小生成树

  • 实现Kruskal算法的辅助数据结构是什么?其作用是什么?
    vset[MAXV]集合辅助数组,2个顶点集合编号不同,加入边不会形成回路。
    代码:
typedef struct {
   int u;      //边的起始顶点
   int v;      //边的终止顶点
   int w;      //边的权值
}Edge;
//改进的克鲁斯卡尔算法(使用了堆排序,并查集)
void Kruskal(AdjGraph* g)
{
      int i,j,k,u1,v1,sn1,sn2;
      UFSTree t[MAXSize];     //并查集,树结构
      ArcNode* p; 
      Edge E[MAXSize];
      k=1;      //     E数组的下标从1开始计
      for(i = 0; i < g.n; i++)
      {
           p=g->adjlist[i].firstarc;
           while(p!=NULL)
           {
               E[k].u=i;
               E[k].v=p->adjvex;
               E[k].w=p->weight;
               k++;
               p=p->nextarc;
           }
      }
      HeapSort(E,g.e);      //采用堆排序对E数组按权值递增排序
      MAKE_SET(t,g.n);      //初始化并查集树t
      k=1;      //k表示当前构造生成树的第几条边,初值为1
      j=1;      //E中边的下标,初值为1
      while(k<g.n)      //生成的边数为n-1
      {
            u1=E[j].u;
            v1=E[j].v;      //取一条边的头尾顶点编号u1和v1
            sn1=FIND_SET(t,u1);
            sn2=FIND_SET(t,v1);      //分别得到两个顶点所属的集合编号
            if(sn1!=sn2)      //两顶点属不同集合
            {
                 k++;      //生成边数增1
                 UNION(t, u1, v1);      //将u1和v1两个顶点合并
            }
            j++;      //下一条边
      }
}
  • 分析Kruskal算法时间复杂度,适用什么图结构,为什么?
    克鲁斯卡尔算法:按权值的递增顺序选择合适的边来构造最小生成树,选取的边不能使生成树形成回路。
    克鲁斯卡尔算法的时间复杂度为O(elog2e)。由于它只与边的条数e有关,所以克鲁斯卡尔算法适合于稀疏图,图的存储结构为邻接表。

1.4 最短路径

1.4.1 Dijkstra算法求解最短路径


其解法如下:

  • Dijkstra算法需要哪些辅助数据结构?
    1)用一个一维数组dist[]存放最短路径长度,如dist[j]表示从源点 v->j的最短路径长度,其源点v是默认的。

2)从源点到其他点的最短路径有n-1条,一条最短路径用一个一维数组path表示。

  • Dijkstra算法如何解决贪心算法无法求最优解问题?展示算法中解决的代码
void Dijkstra(MatGraph g, int v)
{
	int dist[MAXV],path[MAXV];
	int s[MAXV];//判断是否访问
	int mindis, i, j, u;
	for (i = 0; i < g.n; i++)
	{
		dist[i] = g.edges[v][i];//初始化距离
		s[i] = 0;
		if (g.edges[v]]i] < INF)//v到i有边,初始化前继结点
		{
			path[i] = v;
		}
		else
		{
			path[i] = -1;
		}
	}
	s[v] = 1;
	for (i = 0; i < g.n; i++)//进行n-1次
	{
		mindis = INF;
		for (j = 0; j < g.n; j++)//找到最小路径的长度
		{
			if (s[j] == 0 && dist[j] < mindis)
			{
				u = j;
				mindis = dist[j];
			}
		}
		s[u] = 1;
		for (j = 0; j < g.n; j++)//修改改变结点后的路径长度
		{
			if (s[j] == 0)
			{
				if (g.edges[u][j] < INF&&dist[u] + g.edges[u][j] < dist[j])//修改此处可得到各种多种解法
				{
					dist[j] = dist[u] + g.edges[u][j];
					path[j] = u;
				}
			}
		}
	}
}
  • Dijkstra算法的时间复杂度
    算法中涉及到要循环n次(顶点个数)直到所有顶点的最短路径都求出来,且在循环中又要循环n次以来选取不在S中(即在U中)
    的顶点且具有求小最短路径长度的顶点,这里用了两层循环,考虑最坏情况,时间复杂度为O(n^2).

1.4.2 Floyd算法求解最短路径

  • Floyed算法:
    Floyd算法求解最短路径是每个顶点之间的,也可以Dijkstra调用n次,可能达到求解得出每个顶点之间的最短路径。两个时间的复杂性都是O(n^3),不过Floyd形式上更简单点。

Floyd用一个二维数组A存放当前顶点最短路径长度,如:A[i][j]代表低你干点i到j之间的最短路径。
还有一个path二维数组,和之前Dijkstra算法中的path的用途一致,用来回溯寻找路径进过的顶点,Floyd中的是每个顶点的集合成二维数组。

  • Floyd算法需要哪些辅助数据结构
    二维数组A用于存放当前顶点之间的最短路径长度,即分量A[i][j]表示当前i->j的最短路径长度。
  • Floyd算法优势,举例说明
    Floyd算法的优势:它能一次求得任何两个节点之间的最短路径,而Dijkstra算法只能求得以特定节点开始的最短路径。
    算法代码:
#include<stdio.h>
#include<stdlib.h>
#define max 1000000000
 
int d[1000][1000],path[1000][1000];
int main()
{
    int i,j,k,m,n;
    int x,y,z;
    scanf("%d%d",&n,&m);
     
    for(i=1;i<=n;i++)
        for(j=1;j<=n;j++){
            d[i][j]=max;
            path[i][j]=j;
    }
     
    for(i=1;i<=m;i++) {
            scanf("%d%d%d",&x,&y,&z);
            d[x][y]=z;
            d[y][x]=z;
    }
     
    for(k=1;k<=n;k++)
        for(i=1;i<=n;i++)
            for(j=1;j<=n;j++) {
                if(d[i][k]+d[k][j]<d[i][j]) {
                    d[i][j]=d[i][k]+d[k][j];
                    path[i][j]=path[i][k];
                }
            }
    for(i=1;i<=n;i++)
        for(j=1;j<=i;j++)
          if (i!=j) printf("%d->%d:%d\n",i,j,d[i][j]);
    int f, en;
    scanf("%d%d",&f,&en);
    while (f!=en) {
        printf("%d->",f);
        f=path[f][en];
    }
    printf("%d\n",en);
    return 0;
}

1.5 拓扑排序

  • 找一个有向图,并求其对要的拓扑排序序列

    上有向图的一种拓扑序列1->2->4->3->5;
  • 实现拓扑排序代码,结构体如何设计?
    结构体定义:
typedef struct
 {
   Vertex data;
   int count;
   ArcNode *firstarc;
 }VNode;

代码:

void TopSort(AdjGraph *G)	
{      
        int i,j;
        int St[MAXV],top=-1;	
        ArcNode *p;
        for (i=0;i<G->n;i++)		
	G->adjlist[i].count=0;
        for (i=0;i<G->n;i++)		
        {	
            p=G->adjlist[i].firstarc;
	    while (p!=NULL)
	   {        
                  G->adjlist[p->adjvex].count++;
	          p=p->nextarc;
	   }
        }
         
         for (i=0;i<G->n;i++)	
	 if (G->adjlist[i].count==0)
	 {	
            top++;
	    St[top]=i;
	 }
         while (top>-1)			
         {	  
            i=St[top];top--;			
	    printf("%d ",i);		
	    p=G->adjlist[i].firstarc;		
	    while (p!=NULL)	
	    {      
                 j=p->adjvex;
	         G->adjlist[j].count--;
	         if (G->adjlist[j].count==0)	
	         {      
                   top++;
		   St[top]=j;
	         }
	         p=p->nextarc;		
	    }
        }
}

1.6 关键路径

  • AOE网:带权的有向无环图,图中入度为0的顶点表示工程的开始事件,出度为0的顶点表示工程的结束事件,称这样的有向图为边表示活动的网(AOE网)。

通常每个工程都只有一个开始事件和结束事件,工程的AOE网都只有入度为0的顶点,称为源点,和一个出度为0的顶点,称为汇点。

  • 关键路径:在AOE网中从源点到汇点的所有路径中最大路径长度的路径。

  • AOE网中一条关键路径各活动持续时间的总和,把关键路径上的活动称为关键活动。

2.PTA实验作业

2.1 六度空间

2.1.1 伪代码

int BFS(MGraph g,int v)//广搜
{
    int w;
    int tail,last;
    int count=0,level=0;
    int visited[MAXV]={0};
    queue<int> q;
    
    源点v入队列,同时标记v已访问过;
    用last标记顶点v;
    空间加一;
    
    while(q不空)  do
        int j;
        对头元素出队,w=q.front(),队列长度减一;
    
        for   j=1   to    g.n   do
            if  visit[j]==0&&g.edges[w][j]   then 
                顶点j入队列,且标记已访问;
                  用tail标记顶点v,记录当前圈的最后一个顶点编号;
                  空间加一;
            end if
        end for
    
        if   遍历一圈即 last == temp   then 
            层数加一,并记录当前层的最后一个顶点;
        if   遍历6层即level=6   then
            break;
    end while
}

2.1.2 提交列表

2.1.3 本题知识点

知识点:利用邻接矩阵进行广度遍历,通过广度遍历进行层数的判断,需要引入last和tail进行结点访问的层数判断以及结点层数的改变,通过比较last可以判断层数是否需要改变,并及时返回数量。

posted @ 2021-07-06 20:31  网络2012王历  阅读(65)  评论(0编辑  收藏  举报