zoukankan      html  css  js  c++  java
  • DS博客作业04--图

    这个作业属于哪个班级 数据结构--网络2011/2012
    这个作业的地址 DS博客作业04--图
    这个作业的目标 学习树结构设计及运算操作
    姓名 邓宏

    0.PTA得分截图

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

    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 邻接矩阵和邻接表表示图的区别

    对于一个具有n个顶点e条边的无向图
    它的邻接表表示有n个顶点表结点2e个边表结点
    对于一个具有n个顶点e条边的有向图
    它的邻接表表示有n个顶点表结点e个边表结点
    如果图中边的数目远远小于n2称作稀疏图,这是用邻接表表示比用邻接矩阵表示节省空间;
    如果图中边的数目接近于n2,对于无向图接近于n*(n-1)称作稠密图,考虑到邻接表中要附加链域,采用邻接矩阵表示法为宜。
    故邻接矩阵适合稠密图,时间复杂度为O(n^2)
    链表存储数据,适合稀疏图,时间复杂度为O(nlogn)

    1.2 图遍历

    1.2.1 深度优先遍历

    深度优先遍历顺序(v1开始):v1->v2->v0->v3

    深度遍历代码

    //邻接矩阵
    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;
        }
    }
    

    适用案例如:
    是否有简单路径?

    #include <stdio.h>
    #include <malloc.h>
    #include "graph.h"
    int visited[MAXV];     //定义存放节点的访问标志的全局数组
    void ExistPath(ALGraph *G,int u,int v, bool &has)
    {
        int w;
        ArcNode *p;
        visited[u]=1;
        if(u==v)
        {
            has=true;
            return;
        }
        p=G->adjlist[u].firstarc;
        while (p!=NULL)
        {
            w=p->adjvex;
            if (visited[w]==0)
                ExistPath(G,w,v,has);
            p=p->nextarc;
        }
    }
    
    void HasPath(ALGraph *G,int u,int v)
    {
        int i;
        bool flag = false;
        for (i=0; i<G->n; i++)
            visited[i]=0; //访问标志数组初始化
        ExistPath(G,u,v,flag);
        printf(" 从 %d 到 %d ", u, v);
        if(flag)
            printf("有简单路径
    ");
        else
            printf("无简单路径
    ");
    }
    
    int main()
    {
        ALGraph *G;
        int A[5][5]=
        {
            {0,0,0,0,0},
            {0,0,1,0,0},
            {0,0,0,1,1},
            {0,0,0,0,0},
            {1,0,0,1,0},
        };  //请画出对应的有向图
        ArrayToList(A[0], 5, G);
        HasPath(G, 1, 0);
        HasPath(G, 4, 1);
        return 0;
    }
    

    问题:假设图G采用邻接表存储,设计一个算法,判断顶点u到v是否有简单路径。

    1.2.2 广度优先遍历

    广度优先遍历结果(v1开始):v1->v0->v2->v3

    广度遍历代码

    //邻接矩阵
    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; //找下一个邻接点
            }
        }
    }
    

    适用案例如:
    广度优先求解迷宫问题最短路径

    include<Queue.h>
    
    #pragma once
    
    #define MaxSize 100
    
    typedef struct
    
    {
    
        int i, j;    //方块在地图中的位置
    
        int pre; //该路径中上一个方块在队列中的下标
    
    }Box;    //方块类型
    
     
    
    typedef struct
    
    {
    
        Box data[MaxSize];
    
        int front, rear;
    
    }Queue;      //用于存放路径的队列
    
     
    
    void InitQueue(Queue *& q)
    
    {
    
        q = (Queue *)malloc(sizeof(Queue));
    
        q->front = q->rear = -1;
    
    }
    
     
    
    void DestroyQueue(Queue *&q)
    
    {
    
        free(q);
    
    }
    
     
    
    bool QueueEmpty(Queue *q)
    
    {
    
        return (q->front == q->rear);
    
    }
    
     
    
    bool enQueue(Queue *& q, Box e)
    
    {
    
        if (q->rear == MaxSize - 1)
    
             return false;
    
        q->rear++;
    
        q->data[q->rear] = e;
    
        return true;
    
    }
    
     
    
    bool deQueue(Queue *& q, Box & e)
    
    {
    
        if (q->rear == q->front)
    
             return false;
    
        q->front++;
    
        e = q->data[q->front];
    
        return true;
    
    }
    
    
    
    cpp:
    
    #include <iostream>
    
    #include "Queue.h"
    
    #define M 8
    
    #define N 8
    
    using namespace std;
    
     
    
    int map[M + 2][N + 2] = {  //地图,1为不可走,0为可走
    
    {1,1,1,1,1,1,1,1,1,1},{1,0,0,1,0,0,0,1,0,1},{1,0,0,1,0,0,0,1,0,1},{1,0,0,0,0,1,1,0,0,1},{1,0,1,1,1,0,0,0,0,1},
    
    {1,0,0,0,1,0,0,0,0,1},{1,0,1,0,0,0,1,0,0,1},{1,0,1,1,1,0,1,1,0,1},{1,1,0,0,0,0,0,0,0,1},{1,1,1,1,1,1,1,1,1,1}
    
    };
    
     
    
     
    
    void ShowPath(Queue *qu, int front)     //输出正确路径
    
    {
    
        int p = front, p0;
    
        do
    
        {
    
             p0 = p;
    
             p = qu->data[p].pre;
    
             qu->data[p0].pre = -1;//将正确路径的pre记为-1用以标记
    
        } while (p != 0); //利用循环反向找出正确路经
    
        cout << "最短路径:" << endl;
    
        for (int k = 0; k < MaxSize; k++)
    
        {
    
             if (qu->data[k].pre == -1)
    
             {
    
                 cout << "(" << qu->data[k].i << "," << qu->data[k].j << ")";
    
                 cout << "->";
    
             }
    
        }
    
    }
    
     
    
    bool Path(int x0, int y0, int x, int y) //广度优先找最优解
    
    {
    
        int i, j, i0, j0; //i,j存储当前方块位置,i0,j0存储找到新路径的位置
    
        Box e;   //当前方块
    
        Queue * qu;
    
        InitQueue(qu);
    
        e.i = x0;    //设置起点位置
    
        e.j = y0;
    
        e.pre = -1;  //起点pre设置为-1;
    
        enQueue(qu, e);   //起点入队
    
        map[x0][y0] = -1; //走过的点记为-1,表示不可再走
    
        while (!QueueEmpty(qu))        //队非空时循环
    
        {
    
             deQueue(qu, e);       //出队,用e存储
    
             i = e.i; //记录该点坐标
    
             j = e.j;
    
             if (i == x && j == y)
    
             {
    
                 ShowPath(qu, qu->front);  //打印路径
    
                 DestroyQueue(qu);
    
                 return true;
    
             }
    
             for (int circle = 0; circle < 4; circle++)  //遍历周围的方块,若方块可走就将其进队
    
             {
    
                 switch (circle)   //遍历顺序为:上,右,下,左
    
                 {
    
                 case 0:
    
                      i0 = i - 1;
    
                      j0 = j;
    
                      break;
    
                 case 1:
    
                      i0 = i;
    
                      j0 = j + 1;
    
                      break;
    
                 case 2:
    
                      i0 = i + 1;
    
                      j0 = j;
    
                      break;
    
                 case 3:
    
                      i0 = i;
    
                      j0 = j - 1;
    
                      break;
    
                 }
    
                 if (map[i0][j0] == 0) //移动至新位置
    
                 {
    
                      e.i = i0;
    
                      e.j = j0;
    
                      e.pre = qu->front;    //用pre记录队列的数据下标
    
                      enQueue(qu, e);
    
                      map[i0][j0] = -1;
    
                 }
    
             }
    
        }
    
        DestroyQueue(qu); //若队为空,说明遍历所有可以到达的方块后依旧找不到出口,即为无解
    
        return false;
    
    }
    
     
    
    int main()
    
    {
    
        if (!Path(1, 1, 8, 8))
    
             cout << "迷宫无正确路径";
    
        return 0;
    
    }
    
    

    1.3 最小生成树

    一个有 n 个结点的连通图的生成树是原图的极小连通子图,且包含原图中的所有 n 个结点,并且有保持图连通的最少的边。最小生成树可以用kruskal(克鲁斯卡尔)算法或Prim(普里姆)算法求出。

    1.3.1 Prim算法求最小生成树







    代码实现:

    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;
                }
            }
        }
        
    }
    

    实现Prim算法的2个辅助数组是什么?其作用是什么?Prim算法代码。
    辅助数组:
    1.closest保存路径
    2.lowest存储一点到各点间的距离,同时判断顶点是否加入U集合
    Prim算法中有两重for循环,时间复杂度为O(n^2),由于他执行时间与图中边数e关系无关,所以特别适合稠密图求最小生成树

    1.3.2 Kruskal算法求解最小生成树

    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++;//进行下一条边
        }
    }
    

    采用邻接矩阵,目的是为频繁地取一条条边的权
    时间复杂度为O(elog2e),执行时间仅与图中的边数有关,与顶点数无关,故适用于稀疏图建数

    1.4 最短路径

    1.4.1 Dijkstra算法求解最短路径

    辅助数组:
    dist存储某点到各点间的最短距离
    path存储各点距离最短的前驱

    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算法求解最短路径

    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;
    				}
    			}
    		}
    	}
    } 
    
    

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

    1.5 拓扑排序

    结构体:

    typedef struct 	       	//表头结点类型
    {     
          Vertex data;         	//顶点信息
          int count;           	//存放顶点入度
          ArcNode *firstarc;   	//指向第一条边
    }VNode;
    
    

    代码:

    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;
    				}
    			}
    		}
    	}
    } 
    
    

    伪代码:

    void TopSort(AdjGraph* G)
    {
    	将count置初值0
    	再将所有顶点的入度记录在count中
    	遍历count
    	    if count==0  进队列
    
    	遍历队列
    	    输出顶点
    	    所有点count--
    	   如果 count==0  进队列
    }
    

    如何用拓扑排序代码检查一个有向图是否有环路?
    在排序后判断各点count是否为0

    1.6 关键路径

    AOE-网:

    一个工程常被分为多个小的子工程,这些子工程被称为活动,在带权有向图中若以顶点表示事件,有向边表示活动,边上的权值表示该活动持续的时间,这样的图简称为AOE网。---带权有向无环图

    关键路径:

    关键路径是从有向图的源点到汇点的最长路径

    关键活动:

    关键路径中的边叫关键活动

    2.PTA实验作业(4分)

    2.1 六度空间(2分)

    2.1.1 伪代码

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

    2.1.2 提交列表

    2.1.3 本题知识点

    new申请空间:new int* [MAXV + 1]
    dis[]进行距离计算,visited[]进行标记

    2.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 提交列表

    2.2.3 本题知识点

    最短路径Dijkstra算法

  • 相关阅读:
    idea 设置注释
    SVN解决冲突
    mysql执行 sql文件遇到USING BTREE ) ENGINE=MyISAM DEFAULT CHARSET=utf8错误
    如何查看JDK以及JAVA框架的源码
    一道常被人轻视的前端JS面试题
    Js 常用调试的方法
    主要的Ajax框架都有什么?
    Ajax使用的五步法
    Java正则表达式
    查出在当天所处的日期区间的某些数据
  • 原文地址:https://www.cnblogs.com/denghong88/p/14802409.html
Copyright © 2011-2022 走看看