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

    0.PTA得分截图

    1.本周学习总结

    (多对多)

    逻辑结构描述:Graph = (V , E)

    (1)有向图(边有方向)

    V1={A, B, C, D}
    E1={<A,B>, <B,C>, <C,D>,<D,B>,<D,A>}
    例:
    顶点A的一条出边,同时也是顶点B的一条入边;顶点A和顶点B互为邻接点
    度(以顶点i为终点的入边的数目为入度;以顶点i为始点的出边的数目为出度;度=入度+出度):
    A出度为1,入度为1,度为2;B出度为1,入度为2,度为3; 
    

    (2)无向图(边没方向)

    V2={A, B, C, D, E}
    E2={(A,B), (A,E), (B,E),(D,E),(C,B),(C,D)}
    例:
    顶点A和顶点B互为邻接点
    度(以顶点i为端点的边数):
    A度为2;B度为3;C度为2 
    

    (3)完全图

    ①无向图:每两个顶点之间都存在着一条边,称为完全无向图,包含有n(n-1)/2条边

    ②有向图:每两个顶点之间都存在着方向相反的两条边,称为完全有向图,包含有n(n-1)条边

    图接近完全图时,称为稠密图
    相反,当一个图含有较少的边数(即当e<<n(n-1))称为稀疏图
    

    (4)子图

    顶点和边(包括方向)都需是原图的子集

    (5)路径

    ①路径长度:一条路径上经过的总边数
    ②简单路径:一条路径上除开始点和结束点可以相同(也可不相同),其余顶点均不相同
    ③回路或环:一条路径上的开始点与结束点为同一个顶点
    简单回路或简单环:开始点与结束点相同的简单路径

    (6)连通

    ①无向图:
    连通:若从顶点i到顶点j有路径
    连通分量:无向图中的极大连通子图
    连通图:若图中任意两个顶点都连通(有路径)[连通分量只有一个(本身)]

    非连通图:存在若干个不相连接的连通图(有多个连通分量)

    ②有向图:
    强连通图:任意两个顶点之间都存在一条有向路径

    非强连通图:各个强连通子图称作它的强连通分量

    ③找强连通分量:
    在图中找有向环
    扩展:如果某个顶点到该环中任一顶点有路径,并且该环中任一顶点到这个顶点也有路径,则加入这个顶点

    1.1.1图存储结构

    例:

    (1)邻接矩阵(二维数组)

    ①二维数组edges[MAXV][MAXV]表示各个顶点之间关系(一般以行为先
    ②若顶点数很多的情况下,可以引用二级指针
    int **edges;(每个点都需要动态申请内存

    结构体定义(注意结构体定义顺序)

    #define  MAXV  <最大顶点个数>	
    typedef struct
    {    int no;			//顶点编号
         InfoType info;		//顶点信息
    } VertexType;//顶点信息
    typedef struct
    {    int edges[MAXV][MAXV]; 	//邻接矩阵
         int n;                     //边数
         int e;  			//顶点数
         VertexType vexs[MAXV];	//顶点信息
    }  MGraph;//图的定义
    

    MGraph g;
    for (i = 1; i <= n; i++)//初始化二维数组
      for (j = 1; j <= n; j++)
         g.edges[i][j] = 0;
    
    for (i = 0; i < e; i++)//建立顶点之间联系
    {
       cin >> node1 >> node2;
       g.edges[node1][node2] = 1;//无向图建立
       g.edges[node2][node1] = 1;
       //有向图只需g.edges[node1][node2] = 1;
    }
    

    存储空间为O(n2)
    每个图的邻接矩阵表示是唯一的
    适合稠密图的存储

    (2)邻接表

    (顺序表与链表相结合)
    ①对图中每个顶点建立一个单链表,将所有邻接点用链串起来
    ②每个单链表上添加一个表头结点储存顶点信息
    ③将所有表头结点构成一个数组

    结构体定义(注意结构体定义顺序)

    typedef struct ANode
    {     int adjvex;		//该边的终点编号
          struct ANode *nextarc;	//指向下一条边的指针
          InfoType info;		//该边的权值等信息
    }ArcNode;//结点信息,定义在最前面(后面结构体有定义)
    typedef struct Vnode
    {    Vertex data;		
         ArcNode *firstarc;		//指向每个顶点(表头)的第一条边
    }VNode;//顶点信息
    typedef struct 
    {     VNode adjlist[MAXV] ;	//邻接表(表头数组)
           int n;                   //图中顶点数n
           int e;			//图中边数e
    }AdjGraph;//邻接表
    

    AdjGraph*& G
    ArcNode* ptr;
    G = new AdjGraph;
    for (i = 1; i <= n; i++)//初始化
    {
         G->adjlist[i].firstarc = NULL;
         G->adjlist[i].data = i;
    }
    for (i = 0; i < e; i++)//建无向图
    {
    	cin >> node1 >> node2;
    	ptr = new ArcNode;
    	ptr->adjvex = node2;
    	ptr->nextarc = G->adjlist[node1].firstarc;//采用头插法插入结点
    	G->adjlist[node1].firstarc = ptr;//若为无向图,只需单向建立,不需要后序步骤
    
    	ptr = new ArcNode;
    	ptr->adjvex = node1;
    	ptr->nextarc = G->adjlist[node2].firstarc;
    	G->adjlist[node2].firstarc = ptr;
    }
    

    存储空间为O(n+e)
    适合稀疏图的存储


    1.1.2图遍历连通

    例:

    (1)深度优先遍历(DFS)

    (递归)
    选择一个与当前顶点相邻且没被访问过的顶点为初始顶点u,再从u出发进行深度优先搜索,直到图中与当前顶点邻接的所有顶点都被访问过为止

    Int visited[最大顶点数]
    void DFS(ALGraph *G,int v)  
    {    
        ArcNode *ptr;
        visited[v]=1;                   //标记已访问顶点
        cout<<v;		
        ptr=G->adjlist[v].firstarc;      	
        while (ptr!=NULL) 
        {
            if (visited[ptr->adjvex]==0)  //若未被访问过
               DFS(G,ptr->adjvex);    //递归
    	ptr=ptr->nextarc;              	
        }
    }
    

    若图为非连通图(多次调用DFS)

    for (v=0; v<G.vexnum; ++v) 
        if (!visited[v])  
          DFS(G,v);  // 如果还有未访问的顶点再次调用DFS,直到所有顶点都访问过
    

    (2)广度优先遍历(BFS)

    (队列)
    访问当前顶点的所有未被访问过的邻接点,按照先后次序访问每一个顶点的所有未被访问过的邻接点

    void BFS(AdjGraph* G, int v) //v节点开始广度遍历   
    {
        queue<int>qu;
        ArcNode* p;
        int front;//队头元素
        int visited[MAX] = { 0 };//标记已遍历过的结点
        visited[v] = 1;
        qu.push(v);
        while (!qu.empty())
        {
    	front = qu.front();
    	qu.pop();
            cout<<front;
    	p = G->adjlist[front].firstarc;
    	while (p)//遍历当前顶点的整条链
    	{
    		if (!visited[p->adjvex])
    		{
    			visited[p->adjvex] = 1;//标记已遍历过的结点
    			qu.push(p->adjvex);
    		}
    		p = p->nextarc;
    	}
         }
    }
    

    若图为非连通图(多次调用BFS)

    for (v=0; v<G.vexnum; ++v) 
        if (!visited[v])  
          BFS(G,v);  // 如果还有未访问的顶点再次调用BFS,直到所有顶点都访问过
    

    1.1.3最小生成树

    特点:
    连接所有顶点,权值之和最小的生成树
    含有图中全部n个顶点和构成一棵最小生成树有(n-1)条边(可用来判断图是否连通)
    如果在一棵生成树上添加一条边,必定构成一个环

    (1)普里姆Prim算法

    初始化U={v},找到v到其他顶点的所有边为候选边
    重复以下步骤n-1次,使其他n-1个顶点都加入到U中
    在未加入集合U的顶点中找出离集合U中顶点最近的顶点k
    在未加入集合U的顶点中若存在顶点j使得(j,k)的权值小于原来和顶点k关联的候选边,则用(k,j)取代后者作为候选边

    1.closest[i]:最小生成树的边连接的在U中顶点编号
    2.lowcost[i]:顶点i(i属于未加入顶点集U的顶点)到U中顶点的边权重,取最小权重的顶点k加入U
       lowcost[k]=0表示这个顶点在U中
    3.(closest[k],k)构造最小生成树一条边
    

    void Prim(MGraph g,int v)
    {  
       int lowcost[MAXV];
       int min;
       int closest[MAXV];
       int 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)个顶点逐步添加进集合U
       {
          min=INF;
          for (j=0;j<g.n;j++) //在未加入集合U的顶点中找出离集合U中顶点最近的顶点k
               if (lowcost[j]!=0 && lowcost[j]<min)
    	    {
                	min=lowcost[j];  
                    k=j;//记录最近顶点的编号
                }
          printf("边(%d,%d);权为:%d
    ",closest[k],k,min);
          lowcost[k]=0;		//标记k已经加入集合U
          for (j=0;j<g.n;j++)	//修改数组lowcost和closest的值
    	  if (lowcost[j]!=0 && g.edges[k][j]<lowcost[j])
    	  {
                 	lowcost[j]=g.edges[k][j];
                    closest[j]=k;
              }
       }
    }
    

    贪心算法(不需要回溯)

    只考虑当前最优,不从整体最优上考虑,形成的是局部最优解(并不保证得到全局最优解)

    把一个问题分成若干个子问题
    解决每一子问题,得到子问题的局部最优解
    把子问题局部最优解合成,就是问题的一个解
    

    优点:时间空间复杂度小,效率高

    (2)克鲁斯卡尔(Kruskal)算法过程:

    typedef struct 
    {    int u;     //边的起始顶点
         int v;      //边的终止顶点
         int w;     //边的权值
    } Edge; 
    for (i = 1; i <= G->n; i++)//遍历邻接表,将表中顶点之间联系存入结构体数组 E 中
    {
    	p = G->adjlist[i].firstarc;
    	while (p != NULL)
    	{
    		E[k].start = i;
    		E[k].end = p->adjvex;
    		E[k].weight = p->weight;
    		k++;
    		p = p->next;
    	}
    }
    

    可以使用并查集,也可以使用数组(值相同表示相同集合)

    sort(E, E + G->e, cmp); /*bool cmp(Edge a, Edge b)
    //按权值大小升序排序       {
    	                        return a.weight < b.weight;//权值比较
                              }*/
    for (i = 1; i <= G->n; i++)//集合初始化
        vset[i] = i;
    j = 0;
    while (j < k - 1)//遍历完所有边
    {
    	u = E[j].start;//起点
    	v = E[j].end;//终点
    	sn1 = vset[u];
    	sn2 = vset[v];
    	if (sn1 != sn2)//两顶点不属于同集合,不形成回路
    	{
                  cout<<u<<v;
    	      for (i = 1; i <= G->n; i++)//将已连接的边并入同个集合
    		   if (vset[i] == sn2)
    		      vset[i] = sn1;
    	}
    	j++;//下一条边
    }
    

    1.1.4最短路径

    (1)狄克斯特拉Dijkstra算法

    数组dist[]:记录源点V到每个顶点的最短路径长度:初值或无路径用INF(无穷)表示
    数组path[]:最短路径序列的前一顶点的序号;初值或无路径用-1表示
    数组s[]:表示最短路径顶点集合(记录已加入顶点集的顶点)

    void Dijkstra(int start, int end, int city)
    {
       Node dist[MAX];//路径数据
       int s[MAX] = { 0 };//标记已走过结点
       Node mindis;
       for (i = 0; i < city; i++)//dist数组初始化 
       {
    	dist[i].distance = edges[start][i].distance;
    	dist[i].cost = edges[start][i].cost;
       }
       s[start] = 1;//标记起始点已走过
       for (i = 0; i < city; i++)
       {
    	mindis.distance = INF;
    	for (j = 0; j < city; j++)
    	   if (s[j] == 0 && dist[j].distance < mindis.distance)//找距离最短的点
    		{
    			k = j;
    			mindis.distance = dist[j].distance;
    			mindis.cost = dist[j].cost;
    		}
    	s[k] = 1;//标记已走
    	for (j = 0; j < city; j++)
    	   if (s[j] == 0)
    		 if(edges[k][j].distance < INF)//连通
    			if (dist[k].distance + edges[k][j].distance < dist[j].distance)//过k点到j点的新路径比原来短,更新路径长度和价格
    			{
    				dist[j].distance = dist[k].distance + edges[k][j].distance;
    				dist[j].cost = dist[k].cost + edges[k][j].cost;
    			}
    }
    

    (2)弗洛伊德Floyd算法

    二维数组表示dist,path

    行->列最短路径长度:方位值

    行->列最短路径:逆序

    void Floyd(MatGraph g)		//求每对顶点之间的最短路径
    {
    	int A[MAXVEX][MAXVEX];	//建立A数组
    	int path[MAXVEX][MAXVEX];	//建立path数组
    	int i, j, k;
    	for (i = 0; i < g.n; i++)
    		for (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; 	//i和j顶点之间有一条边时
    			else			 //i和j顶点之间没有一条边时
    				path[i][j] = -1;
    		}
    	for (k = 0; k < g.n; k++)		//求Ak[i][j]
    	{
    		for (i = 0; i < g.n; i++)
    			for (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; 	//修改经过顶点k
    				}
    	}
    }
    

    1.1.5拓扑排序

    (有回路,无法拓扑排序,所以拓扑排序可以用来检测图中是否有回路)

    选取一个没有前驱的顶点,输出
    从有向图中删去此顶点以及所有它的出度(并不是在图结构上真实删除)
    重复上述两步,直至图空,或者图不空但找不到无前驱的顶点为止

    该图拓扑排序:两种
    1-2-4-3-5-7
    1-4-2-3-5-7
    
    for (i = 0; i < G->n; i++)
       G->adjlist[i].count = 0;//所有顶点入度初始化为0
    for (i = 0; i < G->n; i++)
    {
    	ptr = G->adjlist[i].firstarc;
    	while (ptr != NULL)//求所有顶点的入度
    	{
    		G->adjlist[ptr->adjvex].count++;
    		ptr = ptr->nextarc;
    	}
    }
    for (i = 0; i < G->n; i++)
    {
    	if (G->adjlist[i].count == 0)//入度为0结点进栈
    	{
    		top++;
    		St[top] = i;
    	}
    }
    while (top > -1)//栈不为空
    {
    	i = St[top];
    	top--;
    	print[k++] = i;
    	ptr = G->adjlist[i].firstarc;
    	while (ptr != NULL)
    	{
    		G->adjlist[ptr->adjvex].count--;//除去与已输出顶点之间联系(并不是真的删除)
    		if (G->adjlist[ptr->adjvex].count == 0)//入度为0结点进栈
    		{
    			top++;
    			St[top] = ptr->adjvex;
    		}
    		ptr = ptr->nextarc;
    	}
    }
    if (k != G->n)//判断是否有回路
    {
    	cout << "error!";
    }
    

    1.1.6关键路径(最长路径)


    ve(v):v作为源点事件最早开始时间为0
    ①ve(v) = Max{ve(x) + 所有相邻顶点边的权值比较}
    vl(v):定义在不影响整个工程进度的前提下,事件v必须发生的时间称为v的最迟开始时间
    ①vl(v)=ve(v) 当v为终点时
    ②vl(v)=MIN{vl(x)-所有相邻顶点边的权值} 其他顶点
    活动a(边)的最早开始时间e(a)指该活动起点x事件的最早开始时间:e(a)=ve(x)
    活动a(边)的最迟开始时间l(a)指该活动终点y事件的最迟开始时间与该活动所需时间之差:l(a)=vl(y)-time

    工程最早可能结束时间:43天
    关键活动:1-3-2-5-6

    1.2.谈谈你对图的认识及学习体会。

    图的结构和关系跟之前学的相比更复杂,应用也很多,有些看了一遍再用,比如最小生成树和最短距离解法会混淆,通过再次复习,更深刻了一点,但是还有些没理解的地方(特别是关键路径)。画图辅助还是很重要!

    >注意点:</span

    ①初始化(邻接表头节点,visited数组,边之间联系等等)和动态申请空间(建邻接表循环内都要新结点都要记得申请内存)
    ②正确判断是使用邻接矩阵还是邻接表,邻接矩阵内存不够可以改为二级指针(动态申请内存)
    ③迭代法和递归设置正确出口
    ④判断图是有向图操作还是无向图,是否连通


    2.阅读代码

    2.1 找到小镇的法官


    2.1.1 该题的设计思路

    信任别人是出度,被人信任是入度,找入度为N-1的人

    2.1.2 该题的伪代码

    定义int* ret_val = (int*)calloc(N + 1, sizeof(int))并初始化
    记录每个人被相信的次数(入度)
       ++ret_val[trust[i][0]]
    如果某个人相信了别人(出度不为0),说明他不是小镇法官,把被相信的次数清零
       ret_val[trust[i][0]] = 0
    查找被相信次数为N-1的人(入度为N-1的人)
    如果有大于1个的法官,说明无法确认身份,否则就为该下标
    

    2.1.3 运行结果

    2.1.4分析该题目解题优势及难点

    优点:利用图结构很好解决了信任和被信任问题,把题目的逻辑关系清楚表达
    难点:数组结构的利用,信任和被信任关系解除很巧妙

    2.2 跳跃游戏 II


    2.1.1 该题的设计思路

    当一次跳跃结束时,从下一个格子开始,到现在能跳到最远的距离,都是下一次跳跃的起跳点

    2.2.2 该题的伪代码

    while(在跳跃范围内)
       for i = start to end do
           求出能跳到最远的距离 maxPos = max(maxPos, i + nums[i])
       end for
       更新下一次起跳点范围开始的格子
       更新下一次起跳点范围结束的格子
       每完成一次跳跃记录跳跃次数
    

    2.2.3 运行结果

    2.2.4分析该题目解题优势及难点

    优点:利用动态规划和贪心算法,只关心怎么去跳不关心跳几次,可以更快得到最小跳跃次数,从局部最优最后化为整体最优
    难点:思路直接看有点难理解后面看了评论解析结合图,才看懂

    2.3 不邻接植花



    2.3.1 该题的设计思路

    1、根据paths建立邻接表
    2、默认所有的花园先不染色
    3、从第一个花园开始走,把与它邻接的花园的颜色从color颜色集中删除
    4、删除所有与它相邻的颜色,在集合中剩下的颜色就可以随机选择
    5、循环第3和4步直到最后一个花园染色完

    2.3.2 该题的伪代码

    利用vector<int> G[N],并根据paths建立邻接表
    for i = 0 to paths.size() do建立无向图
    	G[paths[i][0] - 1].push_back(paths[i][1] - 1);
    	G[paths[i][1] - 1].push_back(paths[i][0] - 1);
    end for	
    vector<int> answer(N, 0)所有的花园先不染色
    for i = 0 to N do
     定义set<int>color{ 1,2,3,4 }保存染色集合
        for j = 0 to G[i].size() do
    	把已染过色从color集合中的去除color.erase()
        end for
        将集合中当前的第一个颜色赋给当前花园answer[i]=*(color.begin())
    end for
    

    2.3.3 运行结果

    2.3.4分析该题目解题优势及难点

    优点:使用邻接矩阵的话会堆栈溢出,改为邻接表法,邻接表的建立跟学的不同用vector连接当链;利用set集合去除相邻已染过的颜色,使得不会重复
    难点:化为图结构做就相对比较简单

  • 相关阅读:
    【JavaWeb】DbUtils入门之QueryRunner
    详解 hibernate mapping配置
    hibernate-mapping的各种属性配置
    Hibernate入门这一篇就够了
    记一次Sql优化过程
    ExtJs双ActionResult共用同一Js文件ID冲突解决方案
    ExtJs批量更新
    枚举初使用
    Dapper连接Oracle
    读取Excel任务列表并显示在Outlook日历上
  • 原文地址:https://www.cnblogs.com/sixiDL000/p/12833474.html
Copyright © 2011-2022 走看看