zoukankan      html  css  js  c++  java
  • 这个作业属于哪个班级 数据结构--网络2011/2012
    这个作业的地址 DS博客作业04--图
    这个作业的目标 学习图结构设计及相关算法
    姓名 喻文康

    0.PTA得分截图

    1.本周学习总结

    1.1 图的存储结构

    1.1.1 邻接矩阵

    图:

    对应邻接矩阵:

    结构体定义:

    typedef struct              //图的定义
    {  int edges[MAXV][MAXV];     //邻接矩阵
       int n,e;              //顶点数,弧数
    } MGraph;                //图的邻接矩阵表示类型
    

    建图函数:

    void CreateMGraph(MGraph& g, int n, int e)//建图 
    {
        int i, j;
        g.n = n;
        g.e = e;
        for (i = 0; i <= g.n; i++)  //初始化邻接矩阵
            for (j = 0; j <= g.n; j++)
                g.edges[i][j] = 0;
    
        for (i = 0; i < g.e; i++)
        {
            int n, m ;
            cin >> n >> m;    //修改邻接矩阵中的值
            g.edges[n][m] = 1;
            g.edges[m][n] = 1;
        }
    }
    

    1.1.2 邻接表

    图:

    对应邻接矩阵:

    结构体定义:

    #define MAX_VERTEX_NUM 10      /*定义最大顶点数*/
    typedef int Vertex;
    typedef struct  ArcNode{       /*表结点*/
    	int adjvex;               /*邻接点域*/
    	struct  ArcNode *nextarc; /*指向下一个表结点*/
    }ArcNode;
    typedef struct VNode{           /*头结点*/
    	Vertex data;                  /*顶点域*/
    	ArcNode *firstarc;          /*指向第一个表结点*/
    }VNode,AdjList[MAX_VERTEX_NUM]; /*AdjList是数组类型*/
    typedef struct { 
    	AdjList vertices;           /*邻接表中数组定义*/
    	int vexnum,arcnum;          /*图中当前顶点数和边数*/
    } ALGraph;                 /*图类型*/
    

    建图函数:

    void CreateAdj(AdjGraph *&G,int n,int e) //创建图邻接表
    {   
        int i,j,a,b;
        ArcNode *p;
        G=new AdjGraph;
        for (i=0;i<n;i++)   G->adjlist[i].firstarc=NULL;
        for (i=1;i<=e;i++)                //根据输入边建图      
          {          
            cin>>a>>b;                    //有向图 
            p=new ArcNode;            //创建一个结点p
            p->adjvex=b;                //存放邻接点
            p->nextarc=G->adjlist[a].firstarc;  //采用头插法插入结点p
            G->adjlist[a].firstarc=p;
           }
          G->n=n; G->e=n;
    }
    

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

    ①对于任一确定的无向图,邻接矩阵是唯一的(行列号与顶点编号一致),但邻接表不唯一(链接次序与顶点编号无关)。

    ②邻接矩阵的空间复杂度为0(n2),而邻接表的空间复杂度为0(n+e)。

    ③在邻接表上容易找到任意一顶点的第一个邻接点和下一个邻接点,但要判定任意两个顶点(vi,vj)之间是否有边或弧相连,则需搜索第i个或第j个链表,还不及邻接矩阵方便。

    ④邻接矩阵多用于稠密图的存储(e接近n(n-1)/2),而邻接表多用于稀疏图的存储(e<<n2)。

    1.2 图遍历

    1.2.1 深度优先遍历

    邻接表图的深度遍历结果(从1开始):

    深度遍历代码:

    void DFS(ALGraph *G, int i) {
    	cout<<i;
    	visited[i] = 1;
    	struct  ArcNode * w;
    	for ( w= G->vertices[i].firstarc;w;w=w->nextarc) {
    		if (visited[w->adjvex] == 0)
    			DFS(G, w->adjvex);
    	}
    }
    

    1.2.2 广度优先遍历

    邻接表图的广度遍历结果(从1开始):

    广度遍历代码:

    void BFS(ALGraph *G,int v)
    {
    	int que[10];
    	int front,rear;
    	front= rear = 0;
    	cout<<v;
    	visited[v] = TRUE;
    	que[rear++] = v;
    	while(rear!=front)
    	{
    		int i = que[front++];
    		ArcNode *tmp = G->vertices[i].firstarc;
    		while(tmp)
    		{
    			if(!visited[tmp->adjvex])
    			{
    				printf(" %d",tmp->adjvex);
    				visited[tmp->adjvex] = TRUE;
    			
    				que[rear++] = tmp->adjvex;
    					
    			}
    			tmp = tmp->nextarc;
    		}
    		
    	}
    }
    

    1.3 最小生成树

    什么是最小生成树:
    如果是不带权值的图的最小生成树,那么它包含原图中的所有 n 个结点,并且有保持图连通的最少的边
    如果是带权值的图的最小生成树,那么它就是包含原图中的所有 n 个结点,并且所以边的权值的累加和最小
    有些图的最小生成树不唯一

    1.3.1 Prim算法求最小生成树

    算法思想:
    1.先选择一个顶点作为树的根节点,把这个根节点当成一棵树

    2.选择图中距离这棵树最近但是没有被树收录的一个顶点,把他收录在树中,并且保证不构成回路

    3.按照这样的方法,把所有的图的顶点一一收录进树中。

    4.如果没有顶点可以收录

    a.如果图中的顶点数量等于树的顶点数量-->最小生成树构造完成

    b. 如果图中的顶点数量不等于树的顶点数量-->此图不连通

    算法过程
    1.将一个图的顶点分为两部分,一部分是最小生成树中的结点(A集合),另一部分是未处理的结点(B集合)。

    2.首先选择一个结点,将这个结点加入A中,然后,对集合A中的顶点遍历,找出A中顶点关联的边权值最小的那个(设为v),将此顶点从B中删除,加入集合A中。

    3.递归重复步骤2,直到B集合中的结点为空,结束此过程。

    4.A集合中的结点就是由Prime算法得到的最小生成树的结点,依照步骤2的结点连接这些顶点,得到的就是这个图的最小生成树。

    图解

    基于上述图结构求Prim算法生成的最小生成树的边序列:

    实现Prim算法的2个辅助数组是什么?其作用是什么?:
    两个辅助数组:closest[i]数组和lowcost[i]数组
    closest[i]:最小生成树的边依附在U中顶点编号。
    lowcost[i]:表示顶点i (iEV一U)到U中顶点的边权重,取最小权重的项点k加入U。并规定lowcost[k]=O表示这个顶点在U中

    Prim算法代码:

    #define INF 32767    //INF表示oo
    void Prim(MGraph g, int v)
    {
        int lowcost[MAXV], min, closest[MAXV], i, j, k;
        for (i = 0; i < g.n; i++)    //给lowcost[]和closest[]置初值
        {
            lowcost[i] = g.edges[v][i];
            closest[i] = v;
        }
        for (i = 1; i < g.n; i++)    //找出(n-1)个顶点
        {
            min = INF;
            for (j = 0; j < g.n; j++)//在(V-U)中找出离U最近的顶点
                if (lowcost[j] != 0 && lowcost[j] < min)
                {
                    min = lowcost[j]; k = j;    //k记录最近顶点的编号
                }
            printf("边(%d,%d)权为:%d
    ", closest[k], k, min);
            lowcost[k] = 0;    //标记k已经加入U
            for (j = 0; j < g.n; j++)    //修改数组lowcost和closest
                if (g.edges[k][j] != 0 && g.edges[k][i] < lowcost[j]        // 修正
                    {
                    lowcost[j] = g.edges[k][i];
                    closest[j] = k;
                    }
        }
    }
    

    时间复杂度:O(n平方)
    适用邻接矩阵图结构

    1.3.2 Kruskal算法求解最小生成树

    算法思想:
    使用贪心算法,每次获取权重最小的边,但是不能让生成树构成回路。直到去到V-1条边为止。

    算法过程
    1.将图各边按照权值进行排序

    2.将图遍历一次,找出权值最小的边,(条件:此次找出的边不能和已加入最小生成树集合的边构成环),若符合条件,则加入最小生成树的集合中。不符合条件则继续遍历图,寻找下一个最小权值的边。

    3.递归重复步骤1,直到找出n-1条边为止(设图有n个结点,则最小生成树的边数应为n-1条),算法结束。得到的就是此图的最小生成树。

    图解

    基于上述图结构求Kruskal算法生成的最小生成树的边序列:

    实现Kruskal算法的辅助数据结构是什么?其作用是什么?
    1.线性结构:图中边的存储需要定义边数组存放
    2.图结构:图存放各顶点信息以及每条边的信息,图要用邻接表来存储更适合

    Kruskal算法代码

    void Kruskal(AdjGraph* g)
    {
    	int i, j,u1,v1,sn1,sn2,k;
    int vset[MAXV]; // 集合辅助数组
    Edge E[MaxSizel; //存放所有边
    k=0;	//E数组的下标从0开始计
    for (i = 0; i < g.n; i++) // 由g产生的边集E
    {
    	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;
    	}
    	InsertSort(E,g.e); //用直接插入排序对E数组按权值递增排序
    	for (i = 0; i < g.n; i++)//初始化辅助数组
    		vset[i] = i;
    	k = 1;//k表示当前构造生成树的第几条边,初值为1
    	j = 0;//E中边的下标,初值为0
    	while (k < g.n)//生成的顶点数小于n时循环
    	{
    		u1 = E[j].u; v1 = E[j].v;// 取一条边的头尾顶点
    		sn1 = vset[u1];
    		sn2 = vset[v1];//分别得到两个顶点所属的集合编号
    		if (sn1 != sn2)//两顶点属于不同的集合
    		{
    			printf(" (%d,%d):%d
    ", u1,v1,E[].w);
    			k++;// 生成边数增1
    			for (i = 0; i < g.n; i++)// 两个集合统一编号
    				if (vset[i] == sn2)//集合编号为sn2的改为sn1
    					vset[i] = sn1;
    		}
    		j++;//扫描下一条边
    	}
    }
    

    1.4 最短路径

    1.4.1 Dijkstra算法求解最短路径

    Dijkstra算法是针对单源点求最短路径的算法。

    主要思路如下:

    1. 将顶点分为两部分:已经知道当前最短路径的顶点集合Q和无法到达顶点集合R。

    2. 定义一个距离数组(distance)记录源点到各顶点的距离,下标表示顶点,元素值为距离。源点(start)到自身的距离为0,源点无法到达的顶点的距离就是一个大数(比如Infinity)。

    3. 以距离数组中值为非Infinity的顶点V为中转跳点,假设V跳转至顶点W的距离加上顶点V至源点的距离还小于顶点W至源点的距离,那么就可以更新顶点W至源点的距离。即下面distance[V] + matrix[V][W] < distance[W],那么distance[W] = distance[V] + matrix[V][W]。

    4. 重复上一步骤,即遍历距离数组,同时无法到达顶点集合R为空。

    Dijkstra算法如何解决贪心算法无法求最优解问题
    在每次循环结束前都要对path[]数组和dist[]数组进行调整

    for (j = 0; j < g.n; j++)//修改不在s中的顶点的距离
    {
    	if (s[j] == 0)
    		if (g.edges[u][j] < INF && dist[u] + g.edges[u][i] < dist[j])
    		{
    			dist[j] = dist[u] + g.edges[u][j];
    			path[j] = u;
    		}
    }
    

    Dijkstra算法的时间复杂度,使用什么图结构,为什么
    算法时间复杂度为O(n的平方),图存储结构为邻接矩阵,因为每次都要访问图里的数据,用邻接矩阵更高效。

    1.4.2 Floyd算法求解最短路径

    Floyd算法(Floyd-Warshall algorithm)又称为弗洛伊德算法、插点法,是解决给定的加权图中顶点间的最短路径的一种算法,可以正确处理有向图或负权的最短路径问题,同时也被用于计算有向图的传递闭包。该算法名称以创始人之一、1978年图灵奖获得者、斯坦福大学计算机科学系教授罗伯特·弗洛伊德命名。

    适用范围:无负权回路即可,边权可正可负,运行一次算法即可求得任意两点间最短路。

    优缺点
    Floyd算法适用于APSP(AllPairsShortestPaths),是一种动态规划算法,稠密图效果最佳,边权可正可负。此算法简单有效,由于三重循环结构紧凑,对于稠密图,效率要高于执行|V|次Dijkstra算法。

    优点:容易理解,可以算出任意两个节点之间的最短距离,代码编写简单

    缺点:时间复杂度比较高,不适合计算大量数据。

    时间复杂度:O(n3);空间复杂度:O(n2);

    1.5 拓扑排序

    在一个有向图中,对所有的节点进行排序,要求没有一个节点指向它前面的节点。

    先统计所有节点的入度,对于入度为0的节点就可以分离出来,然后把这个节点指向的节点的入度减一。

    一直做改操作,直到所有的节点都被分离出来。

    如果最后不存在入度为0的节点,那就说明有环,不存在拓扑排序,也就是很多题目的无解的情况。

    演示图

    拓扑排序伪代码

    遍历邻接表
    计算每个顶点的入度,存入头结点count成员遍历图顶点
    若发现入度为0顶点,入栈st
    while (栈不空)
    {
    	出栈节点v,访问。
    		遍历v的所有邻接点
    	{
    	所有邻接点的入度 - 1
    	若有邻接点入度为0,则入栈st
    	}
    }
    

    拓扑排序代码

    void TopoSort(ALGraph* G, int n)
    {
        int i, j, k, top, m = 0;
        EdgeNode* p;
        int* d = (int*)malloc(n * sizeof(int));
        for (i = 0; i < n; i++)		//初始化数组
        {
            d[i] = 0;
        }
        for (i = 0; i < n; i++)		//统计各个顶点的入度情况,并把他们填入数组里面
        {
            p = G->adjlist[i].firstedge;
            while (p != NULL)
            {
                j = p->adjvex;
                d[j]++;
                p = p->next;
            }
        }
        top = -1;
        for (i = 0; i < n; i++)			//先找出里面入度是0的顶点
        {
            if (d[i] == 0)
            {
                d[i] = top;
                top = i;
            }
        }
    
        while (top != -1)
        {
            j = top;
            top = d[top];
            printf("%d ", j);
            m++;		//统计顶点
            p = G->adjlist[j].firstedge;
            while (p)
            {
                k = p->adjvex;		//相l连接的顶点
                d[k]--;		//相连接的顶点入度减1
                if (d[k] == 0)		//如果发现入度为0的新顶点,从该顶点出发
                {
                    d[k] = top;
                    top = k;
                }
                p = p->next;
            }
    
        }
        if (m < n) printf("
    有回路!
    ");
        free(d);
    }
    

    1.6 关键路径

    1.6.1 什么叫AOE-网

    • 在现代化管理中,人们常用有向图来描述和分析一项工程的计划和实施过程,一个工程常被分为多个小的子工程,这些子工程被称为活动(Activity),在带权有向图中若以顶点表示事件,有向边表示活动,边上的权值表示该活动持续的时间,这样的图简称为AOE网

    1.6.2 什么是关键路径概念

    • 关键路径是指设计中从输入到输出经过的延时最长的逻辑路径。优化关键路径是一种提高设计工作速度的有效方法

    1.6.3 什么是关键活动?

    • 关键活动是为准时完成项目而必须按时完成的活动。即处于关键路径上的活动。所有项目都是由一系列活动组成,而在这些活动中存在各种链接关系和活动约束。其中有些活动如果延误就会影响整个项目工期。在项目中总存在这样一类直接影响项目工期变化的活动,这些活动就是关键活动。

    • 另外,还有次关键活动,次关键活动亦称“准关键活动”。在次关键路径上的活动。即总时差短或时差很小的活动。其路长仅次于关键路径路长。与之对应的还有关键活动和松弛活动。关键活动指处于关键路径上的任何活动,而松弛活动指具有很大时差的活动。

    2.PTA实验作业

    2.1 六度空间

    2.1.1 解题思路(伪代码)

     建立邻接表g,遍历g
      visited[]判断结点,访问为1,否为0
      q.push(x)//先将x放入队列中
      visited[x]=0;
      while(!q.enmpty())//当q不为空时
     {
       temp=q.front();
        p访问q中队头所对应的邻接表结点
        q.pop();
        while(p!=NULL)
       { 
        如果p未被访问 {入队,这样子当访问到最后该层的最后一个结点时,储存的会是下一层最后一个结点的位置 }
        p=p->nextarc   访问p的下一个邻接点
       }
       if(temp=last) 此时找到了下一层结点的最后一个元素位置,将其值给last}
     }
       return sum
    

    2.1.2 提交列表

    2.1.3 本题知识点

    • 1.首先需要图的创建(邻接表/邻接矩阵)
    • 2.图的广度搜索bfs遍历图结构
    • 3.队列的用法及队列各函数的作用

    2.2 村村通

    2.2.1 解题思路(伪代码)

    最小堆存边结点,然后直接用kruskal建最小生成树的思想累计成本,用并查集判断结点间是否连通
    就是对每一个已经入队的点一个一个遍历寻找下一个最短路径
    但是,更好的方法是对每一个点入队,更新到未入队的点的最短距离。就是,距离比原先的距离小,就更新。
    这样每次对每个点更新那么就能保证lowCost中的距离在当前状态下就是最小的
    然后每次让距离最短的入队就可以了
    

    2.2.2 提交列表

    2.2.3 本题知识点

    • 1.能够用邻接矩阵建图
    • 2.能够使用prime算法或kruskal算法求最短路径
  • 相关阅读:
    java窗口按钮位置设置
    使用java语言编写窗口按钮
    添加无参的构造方法
    冒泡排序
    多态
    首页列表显示全部问答,完成问答详情页布局。
    制作首页的显示列表。
    发布功能完成。
    登录之后更新导航
    完成登录功能,用session记住用户名
  • 原文地址:https://www.cnblogs.com/ywk2002/p/14801775.html
Copyright © 2011-2022 走看看