第6章 图的学习总结(邻接矩阵&邻接表)
我觉得图这一章的学习内容更有难度,其实图可以说是树结构更为普通的表现形式,它的每个元素都可以与多个元素之间相关联,所以结构比树更复杂,然而越复杂的数据结构在现实中用途就越大了,功能与用途密切联系,所以,图结构非常重要,学习起来也是有点难度的,在于图的存储结构和逻辑结构,以及它与其他辅助数据结构相结合(链表,队列等),这需要很清晰的逻辑思维,才能把知识贯通。
这么重要的图,它的特别重要应用(最小生成树、最短路径、拓扑排序、关键路径),还有这些应用中一些著名算法,图的这章内容的丰富,让我大开眼界!
学习图最基础的内容,也是实现其他操作最基础、最关键的部分,就是图的存储结构,图的遍历。这里我准备总结一下在做题目时候对邻接矩阵、邻接表,深度优先搜索遍历、广度优先搜索遍历的理解,而对于应用的各种算法,还需要继续学习,才有更深刻的理解。
PTA上题目:列出连通集
给定一个有N个顶点和E条边的无向图,请用DFS和BFS分别列出其所有的连通集。假设顶点从0到N−1编号。进行搜索时,假设我们总是从编号最小的顶点出发,按编号递增的顺序访问邻接点。
输入格式:
输入第1行给出2个整数N(0<N≤10)和E,分别是图的顶点数和边数。随后E行,每行给出一条边的两个端点。每行中的数字之间用1空格分隔。
输出格式:
按照 “ { v1, v2, v3, ... ,vk } ”的格式,每行输出一个连通集。先输出DFS的结果,再输出BFS的结果。
输入样例: 输出样例:
8 6{ 0 1 4 2 7 }0 7{ 3 5 }0 1{ 6 }2 0{ 0 1 2 7 4 }4 1{ 3 5 }2 4{ 6 }3 5
跟据这道题题意,可明显以看出用邻接矩阵的存储结构较容易,而且输入中没有顶点名,直接用数组下标就可以,所以存储输入数据还是比较简单实现的,下面是邻接矩阵存储定义:
typedef int ArcType; //边
type char VerTexType;//顶点名字,这道题不需要用到
typedef struct
{
VerTexType vexs[];//顶点表
ArcType arcs[][];//邻接矩阵
int vexnum,arcnum;//顶点数和边数
}AMGraph;
接下来创建无向图:
void create(AMGraph &G)//邻接矩阵建立无向图
{
int i,j,k;
int x,y;
cin>>G.vexnum>>G.arcnum;
for(i=;i<G.vexnum;i++)//初始化矩阵元素值为0
{
for(j=;j<G.arcnum;j++)
G.arcs[i][j]=;
}
for(k=;k<G.arcnum;k++)//输入一条边的两个端点
{
cin>>x>>y;
G.arcs[x][y]=;//并将对应矩阵中元素值置1,表示此两点间存在边
G.arcs[y][x]=;//无向图的矩阵为对称的
}
}
因为题目的特殊性,找顶点对应的下标的LocateVex函数就省去了,直接用输入数据,所以简单很多。继续是图的遍历,先DFS算法,对于非连通图,需要两个函数来完成图的遍历,刚好这道题目也是输入连通分量,DFSAM函数是对一个连通分量的遍历,DFS是对整个图,有几个连通分量就调用几次DFSAM:
void DFSAM(AMGraph G,int v)//一个连通分量的深度优先搜索遍历
{
int w;
cout<<v<<" ";visit[v]=true;//输出第一个点,同时记录已经访问过
for(w=;w<G.vexnum;w++)
{
if(G.arcs[v][w]!=&&(!visit[w])) DFSAM(G,w);//对尚未访问过且与上一个顶点间存在边的顶点递归调用
}
} void DFS(AMGraph G)//非连通图的深度优先搜索遍历
{
int v;
for(v=;v<G.vexnum;v++)
visit[v]=false;//标记数组初始化
for(v=;v<G.vexnum;v++)
{
if(!visit[v])//对于未访问的顶点调用DFSAMG函数
{ cout<<"{ ";
DFSAM(G,v);
cout<<"}"<<'\n';
}
}
}
继续是广度优先搜索遍历(BFS),感觉相对于DFS在算法上更难一点,需要借助队列的存储结构来完成,这跟上一章树的层次遍历差不多。上课时候讨论过有两种方法可以实现,一是先将顶点入队,再访问;二是先访问顶点,再入队,后者更优,它避免了将已经访问过的顶点进行多余的入队操作。
void BFSAM(AMGraph G,int v)//一个连通分量的广度优先搜索遍历
{
queue<int> q;
int w,x;
cout<<v<<" ";//采用先访问再入队方法
q.push(v);
visit[v]=true;//入队第一个点,同时记录已经访问过
while(!q.empty())//循环下面操作,直到队空
{
x=q.front();//记录此时队头顶点,再出队
q.pop();
for(w=;w<G.vexnum;w++)//遍历查找邻接点
{
if(G.arcs[x][w]!=&&(!visit[w]))//若两点间有边且未访问
{
cout<<w<<" "; //输入此顶点
visit[w]=true;//标记已经访问过
q.push(w);//将此点入队
}
}
}
} void BFS(AMGraph G)//非连通图的深广度优先搜索遍历
{
int v;
for(v=;v<G.vexnum;v++)
visit[v]=false;
for(v=;v<G.vexnum;v++)
{
if(!visit[v])//对于未访问的顶点调用DFSALG函数
{ cout<<"{ ";
BFSAM(G,v);
cout<<"}"<<'\n';
}
}
}
对于上面邻接矩阵查找顶点的下一个邻接点和判断是否有边存在,是通过遍历所有顶点和一个if语句完成,而在邻接表中,这一步操作就不一样了。
邻接矩阵比较熟悉,容易操作,但它适合用在稠密图,空间复杂度高O(n2),稀疏图中尤其浪费空间,所以有时需要采用邻接表。因此,这道题我准备试一下用邻接表,顺便加深一下对算法的理解。邻接表存储结构的定义复杂许多,如下:
typedef struct Arcnode
{
int ad;//顶点所在位置(下标)
struct Arcnode *next;//指向下一条边的指针
}Arcnode; typedef struct Vnode//顶点信息(表)
{
Arcnode *first;
}Vnode,Al[]; typedef struct//邻接表
{
Al ver;//顶点数组
int vexnum,arcnum;//顶点数和边数
}ALGraph;
表头结点和边结点:
然后创建无向图就是对每个指针指向的操作:
void create(ALGraph &G)
{
int i,j,k,x,y;
Arcnode *p,*p1;
cin>>G.vexnum>>G.arcnum;
for(i=;i<G.vexnum;i++)
G.ver[i].first=NULL;
for(k=;k<G.arcnum;k++)//输入一条边的两个端点
{
cin>>x>>y;
p=new Arcnode;//无向图的两点互相指向对方
p->ad=y;p->next=G.ver[x].first;G.ver[x].first=p;
p1=new Arcnode;
p1->ad=x;p1->next=G.ver[y].first;G.ver[y].first=p1;
}
}
开始我在新结点那里出错,出现很奇怪的结果,链式结构的存储确实很容易出现小错误,接着在DFSAL中跟矩阵判断条件有所不同,需要一个指针指向起始位置,在while循环里还有修改指针指向:
void DFSAL(ALGraph G,int v)//一个连通分量的深度优先搜索遍历
{
int w;
cout<<v<<" ";visit[v]=true;
Arcnode *p2;
p2=new Arcnode;
//输出第一个点,同时记录已经访问过
p2=G.ver[v].first;
while(p2)
{
w=p2->ad;
if(!visit[w]) DFSAL(G,w);//对尚未访问过且与上一个顶点间存在边的顶点递归调用
p2=p2->next;
}
}
邻接表的BFS算法操作有点难,我在这里卡了,出现许多错误,这里有队列,有链表,在判断和指针操作有些问题,开始问题出现有:没有定义指针变量指向访问顶点;在while循环之后未修改指针指向,导致死循环;再修改错误过程中,忘记将元素出队还有出队位置不对,运行出错。经过一波修改,最后终于成功了
void BFSAL(ALGraph G,int v)//一个连通分量的广度优先搜索遍历
{
queue<int> q;
int w,x;
cout<<v<<" ";//采用先访问再入队方法
q.push(v);
visit[v]=true;//入队第一个点,同时记录已经访问过
while(!q.empty())//循环下面操作,直到队空
{
x=q.front();//记录此时队头顶点,再出队
Arcnode *p3;
p3=new Arcnode;
p3=G.ver[x].first;
q.pop();
while(p3)
{
w=p3->ad;
if(!visit[w])//若两点间有边且未访问
{
cout<<w<<" "; //输入此顶点
visit[w]=true;//标记已经访问过
q.push(w);//将此点入队
}
p3=p3->next;
}
}
}
然后又发现一个问题,整个程序运行没有问题,但是与题目输出结果在第一个连通分量顺序不同,那究竟在哪有错误,我仔细看了一遍,画一下创建邻接表时候每个点的关系,最后发现是因为存储结构不同,就链表来说,这个创建的时候是前插法,输出顺序与输入顺序有关系,这道题要求“从编号最小的顶点出发,按编号递增的顺序访问邻接点”,就仅限于用邻接矩阵方法实现。
但在这个过程中发现问题,解决问题,更明白许多,特别对不太熟悉的邻接表方法,有更深的理解。下面是完整代码,虽然暂时不适应解决这道题,但以后可能要用到这种思想。
#include<iostream>
#include<queue>
using namespace std;
bool visit[];//访问标记数组
typedef int ArcType; //边 typedef struct Arcnode
{
int ad;
struct Arcnode *next;
}Arcnode; typedef struct Vnode
{
Arcnode *first;
}Vnode,Al[]; typedef struct
{
Al ver;
int vexnum,arcnum;
}ALGraph; void create(ALGraph &G)
{
int i,j,k,x,y;
Arcnode *p,*p1;
cin>>G.vexnum>>G.arcnum;
for(i=;i<G.vexnum;i++)
G.ver[i].first=NULL;
for(k=;k<G.arcnum;k++)//输入一条边的两个端点
{
cin>>x>>y;
p=new Arcnode;
p->ad=y;p->next=G.ver[x].first;G.ver[x].first=p;
p1=new Arcnode;
p1->ad=x;p1->next=G.ver[y].first;G.ver[y].first=p1;
}
} void DFSAL(ALGraph G,int v)//一个连通分量的深度优先搜索遍历
{
int w;
cout<<v<<" ";visit[v]=true;
Arcnode *p2;
p2=new Arcnode;
//输出第一个点,同时记录已经访问过
p2=G.ver[v].first;
while(p2)
{
w=p2->ad;
if(!visit[w]) DFSAL(G,w);//对尚未访问过且与上一个顶点间存在边的顶点递归调用
p2=p2->next;
}
} void DFS(ALGraph G)//非连通图的深度优先搜索遍历
{
int v;
for(v=;v<G.vexnum;v++)
visit[v]=false;//标记数组初始化
for(v=;v<G.vexnum;v++)
{
if(!visit[v])//对于未访问的顶点调用DFSAMG函数
{ cout<<"{ ";
DFSAL(G,v);
cout<<"}"<<'\n';
}
}
} void BFSAL(ALGraph G,int v)//一个连通分量的广度优先搜索遍历
{
queue<int> q;
int w,x;
cout<<v<<" ";//采用先访问再入队方法
q.push(v);
visit[v]=true;//入队第一个点,同时记录已经访问过
while(!q.empty())//循环下面操作,直到队空
{
x=q.front();//记录此时队头顶点,再出队
Arcnode *p3;
p3=new Arcnode;
p3=G.ver[x].first;
q.pop();
while(p3)
{
w=p3->ad;
if(!visit[w])//若两点间有边且未访问
{
cout<<w<<" "; //输入此顶点
visit[w]=true;//标记已经访问过
q.push(w);//将此点入队
}
p3=p3->next;
}
}
} void BFS(ALGraph G)//非连通图的深度优先搜索遍历
{
int v;
for(v=;v<G.vexnum;v++)
visit[v]=false;//标记数组初始化
for(v=;v<G.vexnum;v++)
{
if(!visit[v])//对于未访问的顶点调用DFSAMG函数
{ cout<<"{ ";
BFSAL(G,v);
cout<<"}"<<'\n';
}
}
} int main()
{
ALGraph g;
create(g);
DFS(g);
BFS(g);
return ;
}
这一章还有很多需要学习,这里只是对图的理解和最基础的的操作,后面许多算法还只停留在理解,实际应用还实现不了,接下来需要对图的应用那部分内容有更多的学习和探索。
第6章 图的学习总结(邻接矩阵&邻接表)的更多相关文章
- 网络流三大算法【邻接矩阵+邻接表】POJ1273
网络流的基本概念跟算法原理我是在以下两篇博客里看懂的,写的非常好. http://www.cnblogs.com/ZJUT-jiangnan/p/3632525.html http://www.cnb ...
- 【数据结构】【图文】【oj习题】 图的拓扑排序(邻接表)
拓扑排序: 按照有向图给出的次序关系,将图中顶点排成一个线性序列,对于有向图中没有限定次序关系的顶点,则可以人为加上任意的次序关系,由此所得顶点的线性序列称之为拓扑有序序列.显然对于有回路的有向图得不 ...
- 数据结构学习笔记05图 (邻接矩阵 邻接表-->BFS DFS、最短路径)
数据结构之图 图(Graph) 包含 一组顶点:通常用V (Vertex) 表示顶点集合 一组边:通常用E (Edge) 表示边的集合 边是顶点对:(v, w) ∈E ,其中v, w ∈ V 有向边& ...
- 图的全部实现(邻接矩阵 邻接表 BFS DFS 最小生成树 最短路径等)
1 /** 2 * C: Dijkstra算法获取最短路径(邻接矩阵) 3 * 6 */ 7 8 #include <stdio.h> 9 #include <stdlib.h> ...
- <图论入门>邻接矩阵+邻接表
非本人允许请勿转载. 趁热打铁,学会了邻接表把这个总结一下,以及感谢大佬uncle-lu!!!(奶一波)祝早日进队! 首先,图论入门就得是非常基础的东西,先考虑怎么把这个图读进去. 给定一个无向图,如 ...
- HDU 1874 畅通工程续(最短路/spfa Dijkstra 邻接矩阵+邻接表)
题目链接: 传送门 畅通工程续 Time Limit: 1000MS Memory Limit: 65536K Description 某省自从实行了很多年的畅通工程计划后,终于修建了很多路. ...
- 图的基本操作(基于邻接表):图的构造,深搜(DFS),广搜(BFS)
#include <iostream> #include <string> #include <queue> using namespace std; //表结点 ...
- hdu 1874 畅通工程(spfa 邻接矩阵 邻接表)
题目链接 畅通工程,可以用dijkstra算法实现. 听说spfa很好用,来水一发 邻接矩阵实现: #include <stdio.h> #include <algorithm> ...
- 第六章 图(b1)邻接矩阵
随机推荐
- 观察OnPaint与OnIdle与OnSize事件
import wx class SketchWindow(wx.Window): def __init__(self, parent, ID): wx.Window.__init__(self, pa ...
- 为什么Java中的字符串是不可变的?
原文链接:https://www.programcreek.com/2013/04/why-string-is-immutable-in-java/ java字符串是不可变的.不可变类只是一个不能修改 ...
- LeetCode:跳跃游戏【55】
LeetCode:跳跃游戏[55] 题目描述 给定一个非负整数数组,你最初位于数组的第一个位置.数组中的每个元素代表你在该位置可以跳跃的最大长度.判断你是否能够到达最后一个位置. 示例 1: 输入: ...
- 解析器组件和序列化组件(GET / POST 接口设计)
前言 我们知道,Django无法处理 application/json 协议请求的数据,即,如果用户通application/json协议发送请求数据到达Django服务器,我们通过request.P ...
- 使用wepy 小程序授权点击取消授权失败的方案
在wepy里使用进行小程序页面授权,里面包含了用户点击取消的重新授权方案: //auth.js /* * @Author: Porco_Mar * @Date: 2018-04-11 15:49:55 ...
- Ubuntu编译Android使用的FFmpeg
本文介绍在Ubuntu平台编译FFmpeg库,用于Android使用.前提需要配置好NDK的环境.可以参考之前的文章Android NDK环境搭建. 下载FFmpeg 在官网下载FFmpeg源码,ht ...
- HDU 1201 Fibonacci Again
Fibonacci Again Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 65536/32768 K (Java/Others)T ...
- PHP按照比例随机
有这样的需求,在打开链接的时候,随机(按照项目的某个属性的比例随机)跳转到指定的几个项目的某一个项目页面 比如项目A:80 项目B:20 那么跳转到项目A 的比例为80%,项目B的比例为20% 那么 ...
- 岭回归与Lasso回归
线性回归的一般形式 过拟合问题及其解决方法 问题:以下面一张图片展示过拟合问题 解决方法:(1):丢弃一些对我们最终预测结果影响不大的特征,具体哪些特征需要丢弃可以通过PCA算法来实现:(2):使用正 ...
- codeforces 706B B. Interesting drink(二分)
题目链接: B. Interesting drink 题意: 给出第i个商店的价钱为x[i],现在询问mi能在多少个地方买酒; 思路: sort后再二分; AC代码: #include <ios ...