zoukankan      html  css  js  c++  java
  • 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 a, b;
        //初始化矩阵
        for (int i = 1; i <= MAXV; i++) {
            for (int j = 1; j <= MAXV; j++) {
                g.edges[i][j] = 0;
            }
        }
        //填入对应
        for (int i = 1; i <= e; i++) {
            cin >> a >> b;
            g.edges[a][b] = 1;//无向图需要两个边都为一
            g.edges[b][a] = 1;
        }
        g.e = e;
        g.n = n;
    }
    

    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 a, b;
        ArcNode* p;
        G = new AdjGraph;
        //初始化
        for (int i = 0; i < n; i++)G->adjlist[i].firstarc = NULL;
    
        for (int i = 1; i <= e; i++) {
            cin >> a >> b;
            //建立a与b之间的关系
            p = new ArcNode;
            p->adjvex = b;
            p->nextarc = G->adjlist[a].firstarc;
            G->adjlist[a].firstarc = p;
            //建立b与a之间的关系
            p = new ArcNode;
            p->adjvex = a;
            p->nextarc = G->adjlist[b].firstarc;
            G->adjlist[b].firstarc = p;
        }
        G->n = n; G->e = e;
    }
    

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

    • 邻接矩阵:
      用二维数组存储内容,故时间复杂度为O(n^2),edge[i][j]即可判断两顶点是否相连,但如果在栈区申请则不能太大,可以使用动态内存申请堆区的空间
      邻接矩阵适合稠密图
    • 邻接表:
      用链表存储数据,故时间复杂度为O(nlogn),适合稀疏图
      如果图中边的数目远远小于n^2称作稀疏图,这是用邻接表表示比用邻接矩阵表示节省空间;
      如果图中边的数目接近于n^2,对于无向图接近于n*(n-1)称作稠密图,考虑到邻接表中要附加链域,采用邻接矩阵表示法为宜。

    1.2 图遍历

    1.2.1 深度优先遍历

    • 深度优先遍历--DFS
    //邻接矩阵
    void DFS(MGraph g, int v)//深度遍历 
    {
        int i;
        //控制空格输出
        if (flag==0) {
            cout << v;
            flag = 1; 
        }
        else cout << " " << v;
        visited[v] = 1;//标记已经走过的点
        
        for (i = 1; i <= g.n; i++) {
            if (g.edges[v][i]&&!visited[i]) DFS(g, i);
        }
    }
    //邻接表
    void DFS(AdjGraph* G, int v)//v节点开始深度遍历 
    {
        ArcNode* p;
        visited[v] = 1;//置已访问标记
        //控制空格输出
        if (flag == 0) {
            cout << v; 
            flag = 1;
        }
        else cout << " " << v;
        
        p=new ArcNode;//用于遍历v后面的链表
        p = G->adjlist[v].firstarc;
        while (p != NULL){
            if (!visited[p->adjvex])
                DFS(G, p->adjvex);
            p = p->nextarc;
        }
    }
    
    • 深度遍历的应用
    • 运用深度优先搜索,对一个有向无回路图DAG进行拓扑排序;
    • 用于迷宫求解
    • 可以判断是否为强连通图
    void DFSTraverse(Graph g){
    	int count=0;
    	for(int v=0;v<g.n;v++) visited[v]=FALSE;
    	for(int v=0;v<g.n;v++){
    		if(!visited[v]){
    			DFS(g,v);
    			count++;//记录有几个连通分量
    		}
    	}
    }
    
    

    1.2.2 广度优先遍历


    • 广度优先遍历--BFS
    //邻接矩阵
    void BFS(MGraph g, int v)//广度遍历 
    {
        int f = 0,r=0,k;
        int que[MAXV*5];//队列辅助
    
        //控制空格的输出
        if (flag) {
            cout << v;
            flag = 0;
        }
        visited[v] = 1;//标记已经走过的点
        
        que[r++] = v;
        while (f!=r) {
            k = que[f++];
            for (int j = 1; j <= g.n; j++) {
                if (g.edges[k][j] && !visited[j]) {
                    cout << " " << j;
                    visited[j] = 1;
                    que[r++] = j;
                }
            }
        } 
    }
    
    //邻接表
    void BFS(AdjGraph* G, int v) //v节点开始广度遍历 
    {
        queue<int> q;
        int w;
        ArcNode* p;
        q.push(v);//第一个结点入队列
        visited[v] = 1; 
        cout << v;
    
        while (!q.empty()) {
            w = q.front();//访问队头
            q.pop();
            p = new ArcNode;
            p = G->adjlist[w].firstarc;//访问w第一条边
            while (p != NULL){
                w = p->adjvex;//边的邻接点
                if (!visited[w]){ // 若当前邻接点未被访问
                   q.push(w);//该顶点进队
                   visited[w] = 1;//置已访问标记
                   cout << " " << w;
                }
                p = p->nextarc; //找下一个邻接点
            }
        }
    }
    

    广度遍历应用

    • 图的BFS算法可以用来求从图中一个顶点到其余各个顶点的最短路径。
      如果对图中每个顶点都使用一次BSF,就可以求出从图中每个顶点到其余各个顶点的最短路径

    1.3 最小生成树

    假设,我们要在n个城市中建立一个通信网络,则连通这n个城市需要布置n-1条通信线路,
    这个时候我们需要考虑如何在成本最低的情况下建立这个通信网?---最小生成树
    最小生成树就是将每两个顶点之间的权值最小,成本最低,建立图结构

    • 三个原则
      1.必须只使用该网络中的边来构造最小生成树;
      2.必须使用且仅使用n-1条边来连接网络中的n个顶点;
      3.不能使用产生回路的边。

    1.3.1 Prim算法求最小生成树

    • 大致思路
      从连通图N={V,E}中的某一顶点U0出发,选择与它关联的具有最小权值的边(U0,v),将其顶点加入到生成树的顶点集合U中。以后每一步从一个顶点在U中,而另一个顶点不在U中的各条边中选择权值最小的边(u,v),把它的顶点加入到集合U中。如此继续下去,直到图中的所有顶点都加入到生成树顶点集合U中为止。
      生成的最小生成树的权值是唯一的但是树形可能不唯一
      实现Prim算法的辅助数组lowcost与closest
    • lowcost数组:lowcost[i]存储U-V中 i 顶点到其邻边中的最小边权值
    • closest数组:记录最小权值的边对应的顶点
      例如

    从A开始

    {(A,B)}

    {(A,B),(B,F)}

    {(A,B),(B,F),(F,E)}

    {(A,B),(B,F),(F,E),(E,D)}

    {(A,B),(B,F),(F,E),(E,D),(D,C)}

    {(A,B),(B,F),(F,E),(E,D),(D,C),(E,G)}

    void Prim(MGraph g, int v)
    {
        int lowcost[MAXV], min, closest[MAXV],k;
        for (int i = 0; i < g.n; i++) {//初始化lowcost与closest
            lowcost[i] = g.edges[v][i];
            closest[i] = v;
        }
        for (int i = 1; i < g.n; i++) { //找出(n-1)个顶点
            min = INF;
            for (int j = 0; j < g.n; j++) {//V-U中找出离U最近的顶点k
                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 (int j = 0; j < g.n; j++) {//修改数组lowcost和closest
                if (g.edges[k][j] != 0 && g.edges[k][j] < lowcost[j]) {
                    lowcost[j] = g.edges[k][j];
                    closest[j] = k;
                }
            }
        }
        
    }
    
    
    • Prime普利姆算法求最小生成树时,和边数无关,只和定点的数量相关,适合求稠密图的最小生成树,其时间复杂度为O(n^2),适合用邻接矩阵建的图

    1.3.2 Kruskal算法求解最小生成树

    • 大致思路
      将所有边按照权值的大小进行升序排序,然后从小到大一一判断,
      原则为:如果这个边不会与之前选择的所有边组成回路,就可以作为最小生成树的一部分;反之,舍去。
      直到具有 n 个顶点的连通网筛选出来 n-1 条边为止。筛选出来的边和所有的顶点构成此连通网的最小生成树。
    • 实现Kruskal算法的辅助数据结构是邻接表,收集边时需要遍历图,使用邻接表可以更快的得到所有边信息
      例如


      {(F,E)}

      {(F,E),(D,C)}

      {(F,E),(D,C),(E,D)}

      因为(C,E)与(C,F)会使之形成环路,故舍去{(F,E),(D,C),(E,D),(B,F)}

      {(F,E),(D,C),(E,D),(B,F),(E,G)}

      因为(F,G)与(B,C)会使之形成环路故舍去{(F,E),(D,C),(E,D),(B,F),(E,G),(A,B)}
    void Kruskal(AdjGraph *g)
    {
        int u1, v1, sn1, sn2, k=1,j;
        int vset[MAXV];//集合辅助数组
        UFSTree t[MAXV];//并查集,树结构
        Edge E[MAXV];//存放所有边
        ArcNode* p;
        p = new ArcNode;
        for (int 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;
            }
        }
        sort(E, E + g->n,cmp);//利用快排sort进行权值递增排序
        MakeSet(t, g->n);//初始化并查集树t
        k = 1; //k表示当前构造的树是第几条边
        j = 1;
        while (k < g->n) {
            u1 = E[j].u;
            v1 = E[j].v;
            sn1 = FindSet(t, u1);
            sn2= FindSet(t, v1);//得到两个顶点所属集合
            if (sn1 != sn2) {//集合不同
                printf("(%d,%d):%d
    ", u1, v1, E[j].w);
                k++;//生成边数+1
                Union(t, u1, v1);//将两个顶点合并
            }
            j++;//进行下一条边
        }
    }
    
    • Kruskal算法求最小生成树时,需要找到最小边,故与边数有关,适合求稀疏图的最小生成树,其时间复杂度为O(eloge),适合用邻接表建的图
      Prim与Kruskal比较
    • Prim侧重顶点寻找,Kruskal侧重边的寻找
    • Prim适用于稠密图,Kruskal适用于稀疏图

    1.4 最短路径

    ​从图的一个点到另一个点到路径不止一条,每条路径的长度可能不同,把路径长度最短的那条叫做最短路径

    1.4.1 Dijkstra算法求解最短路径

    Dijkstra算法可求得某个顶点到其他顶点的最短路

    • Dijkstra算法需要dist[]与path[]辅助,dist[]存最短路径长度,path[]存该点的前驱节点

    S U dist[] path[]
    {1} {2,3,4,5,6} 0,7,9,(infty),(infty),14 1,1,1,-1,-1,0

    S U dist[] path[]
    {1,2} {3,4,5,6} 0,7,9,22,(infty),14 1,1,1,2,-1,0

    S U dist[] path[]
    {1,2,3} {4,5,6} 0,7,9,20,(infty),11 1,1,1,3,-1,3

    S U dist[] path[]
    {1,2,3,6} {4,5} 0,7,9,20,20,11 1,1,1,3,6,3

    Dijkstra算法如何解决贪心算法无法求最优解问题?展示算法中解决的代码。

    void Dijkstra(MGraph g,int v){
    	int dist[MAXV],path[MAXV];
    	int s[MAXV];
    	int mindis,u;
    	//dist和path数组初始化 
    	for(int i=0;i<g.n;i++){
    		dist[i]=g.edges[v][i];
    		s[i]=0;
    		if(g.edges[v][i]<INF)path[i]=v;
    		else path[i]=-1;
    	}
    	s[v]=1;//将源点放在S中 
    	for(int i=0;i<g.n;i++){
    		mindis=INF;
    		//找最小路径长度顶点u 
    		for(int j=0;j<g.n;j++){
    			if(s[j]==0&&dist[j]<mindis){
    				u=j;
    				mindis=dist[j];
    			}
    		}
    		s[u]=1;//u加入S 
    		for(int j=0;j<g.n;j++){//修改不在s中的顶点的距离 
    			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;
    				}
    			}
    		}
    	}
    	Dispath(dist,path,s,g.n,v);//输出 
    }
    
    • 时间复杂度为O(n^2),采用邻接矩阵表示

    1.4.2 Floyd算法求解最短路径

    • Floyd算法可以求得任意两个顶点的最短路,
    • Floyd算法需要两个二维数组A[][]与path[][]辅助,
    • Floyd算法优势:
    1. Dijkstra不能处理负权图,Flyod能处理负权图;
    2. Dijkstra需要求dist数组最短路径,Flyod不需要,且代码简短
      例如






    void Floyd(MGraph g){
    	int A[MAXV][MAXV];
    	int path[MAXV][MAXV];
    	for(int i=0;i<g.n;i++){//初始化A与path 
    		for(int j=0;j<g.n;j++){
    			A[i][j]=g.edges[i][j];
    			if(i!=j&&g.edges[i][j]<INF)path[i][j]=i;
    			else path[i][j]=-1;
    		}
    	}
    	for(int k=0;k<g.n;k++){
    		for(int i=0;i<g.n;i++){
    			for(int j=0;j<g.n;j++){
    				if(A[i][j]>A[i][k]+A[k][j]){//找更短路径 
    					A[i][j]=A[i][k]+A[k][j];//进行修改 
    					path[i][j]=k;
    				}
    			}
    		}
    	}
    } 
    
    
    • 该算法时间复杂度为O(N^3),虽然Dijkstra算法也可以求得任意两顶点的最短路,但是Floyd更简洁
    • 无负权回路即可,边权可正可负,运行一次算法即可求得任意两点间最短路。

    1.4.3 SPFA算法求最短路径

    SPFA算法是求解单源最短路径问题的一种算法,
    其优于Dijkstra算法的方面是边的权值可以为负数、实现简单,
    缺点是时间复杂度过高,高达 O(VE),但算法可以进行若干种优化,提高了效率。

    void Spfa(AdjGraph* G,int u) {
        ArcNode* p;
        int dis[MAXV],vis[MAXV];
        for (int i = 1; i <= G->n; i++)dis[i] = INF;//初始化距离为最大值 
        dis[u] = 0;//起点距离为0 
        q.push(u);//入队 
        vis[u] = 1;
    
        while (!q.empty()) {
            int k = q.front();
            vis[k] = 0;//出队 
            p = G->adjlist[k].firstarc;
            while (p != NULL){
                int v = p->adjvex;
                if (dis[v] > dis[k] + p->weight) {//松弛 
                    dis[v] = dis[k] + p->weight;
                    if (!vis[v]) {//没入队就入队 
                        vis[v] = 1;
                        q.push(v);
                    }
                }
                p = p->nextarc;
            }
            q.pop();
        }
    }
    
    

    1.5 拓扑排序

    拓扑排序:是一个有向无环图的所有顶点的线性序列。且该序列必须满足下面两个条件:

    1. 每个顶点出现且只出现一次。
    2. 若存在一条从顶点 A 到顶点 B 的路径,那么在序列中顶点 A 出现在顶点 B 的前面。
      大致思路:
      1.在有向图中选一个没有前驱的顶点并且输出
      2.从图中删除该顶点和所有以它为尾的弧(白话就是:删除所有和它有关的边)
      3.重复上述两步,直至所有顶点输出,或者当前图中不存在无前驱的顶点为止,后者代表我们的有向图是有环的,因此,也可以通过拓扑排序来判断一个图是否有环。

      拓扑排序序列为{1,2,4,3,5}
      伪代码
    void TopSort(AdjGraph* G)
    {
    	将count置初值0
    	再将所有顶点的入度记录在count中
    	遍历count
    	    if count==0  进队列
    
    	遍历队列
    	    输出顶点
    	    所有点count--
    	   如果 count==0  进队列
    }
    
    • 拓扑排序中通过栈或队列将该点移出,实现入度为零的顶点的删除。

    结构体

    typedef struct ANode
    {
    	int adjvex;            //该边的终点编号
    	struct ANode* nextarc;    //指向下一条边的指针
    	int info;    //该边的相关信息,如权重
    } ArcNode;                //边表节点类型
    typedef int Vertex;
    typedef struct {
    	Vertex data;//顶点信息
    	int count;//存放顶点入度
    	ArcNode* firstarc;//指向第一条弧
    }VNode;
    typedef VNode AdjList[MAXV];
    typedef struct
    {
    	AdjList adjlist;        //邻接表
    	int n, e;        //图中顶点数n和边数e
    } AdjGraph;
    

    代码

    void TopSort(AdjGraph* G)//邻接表拓扑排序
    {
        int s[MAXV], top = -1, i,k=0, flag = 0,num[MAXV];
        ArcNode* p;
        for ( i = 0; i < G->n; i++) G->adjlist[i].count = 0;//入读置初值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++) {//将入度为0的顶点进栈
            if (G->adjlist[i].count == 0) {
                s[++top] = i;
            }
            
        }
        while (top > -1) {
            i = s[top--];
            flag++;
            num[k++] = i;
            p = G->adjlist[i].firstarc;
            while (p != NULL) {
                G->adjlist[p->adjvex].count--;
                if (G->adjlist[p->adjvex].count == 0) {//将入度为0的入栈
                    s[++top] = p->adjvex;
                }
                p = p->nextarc;
            }
        }
        if (flag != G->n) {
            cout << "error!";
            return;
        }
        flag = 0;
        for (int j = 0; j < k; j++) {
            if (flag == 0) {
                cout << num[j];
                flag = 1;
            }
            else cout << " " << num[j];
        }
    }
    

    用拓扑排序代码检查一个有向图是否有环
    最后flag不等于结点数,则没有全部输出所有结点,证明还有结点有入度,则该图有环

    1.6 关键路径

    AOE-网?
    一个工程常被分为多个小的子工程,这些子工程被称为活动,在带权有向图中若以顶点表示事件,有向边表示活动,边上的权值表示该活动持续的时间,这样的图简称为AOE网。---带权有向无环图
    关键路径
    关键路径是从有向图的源点到汇点的最长路径
    关键活动
    关键路径中的边叫关键活动

    2.PTA实验作业

    2.1 六度空间

    2.1.1 伪代码

    void BFS(MGraph& g,int u){
    	将u顶点标记
    	初始化dist[u]=0记录距离
    	将u进队列
    	cnt++;//用来表示几个人
    	while q
    	  取出队头,判断距离dist是否大于6
    	  循环矩阵,只要没有被标记并且有边
    	  进队列,距离+1;标记已访问,cnt++
    }
    

    2.1.2 提交列表

    • 刚开始的多种错误是数组开小了,段错误
    • 后来开的过大又错误
    • 用指针动态申请,new知识欠缺。。。

    2.1.3 本题知识点

    1. new申请空间:new int* [MAXV + 1]
    2. dis[]进行距离计算,visited[]进行标记
    3. 每次每个人都要将数组初始化!!!

    2.2 旅游规划

    2.2.1 伪代码

    void Dijkstra(MGraph g, int v)
    {
        初始化dist数组、s数组、pay数组,dist数组
        遍历图中所有节点
    	    for(i = 0; i < g.n; i++)
               若s[i]! = 0,则数组找最短路径,顶点为u
    
    	    s[u] = 1进s
    	    for(i = 0; i < g.n; i++)
    
    	       if(g.edges[u][j].len < INF && dist[u] + g.edges[u][j].len < dist[j])
    		     则修正dist[j] = dist[u] + g.edges[u][j].len;
    				   pay[j] = pay[u] + g.edges[u][j].pay;
    	       else  if(路径一样长但是花费更少)
    		     则修正pay[j] = pay[u] + g.edges[u][j].pay;
    
    }
    

    2.2.2 提交列表


    前面多处错误是因为数组开小了
    最后一直不对,我以为是算法出问题了。一直改,结果发现我建的是有向图。。。。(呜呜呜,找一天bug)

    2.2.3 本题知识点

    • 邻接矩阵,无向图
    • 最短路径Dijkstra算法
    • 用另一个结构体,存路径长度和费用,并用dist[]与pay[]存储
  • 相关阅读:
    Installation request for topthink/think-captcha ^3.0 -> satisfiable by topthink/think-captcha[v3.0.0].
    /etc/sudoers配置错误导致的nova-api等异常
    修改ssh默认端口导致的虚拟机resize失败
    ansible自动化测试云平台多个网络角色间带宽(shell模块调用iperf)
    nova的服务心跳机制和服务状态监控机制的实现
    时间不同步导致的nova,cinder服务一会up一会down的来回跳跃
    利用ansible部署keeplived和haproxy集群
    利用ansible检测网络连通性(多个网段多IP)
    通过ansible安装etcd集群
    部署k8s statefulset
  • 原文地址:https://www.cnblogs.com/ww-yy/p/14798406.html
Copyright © 2011-2022 走看看