图系列之_图的表示
参考:
数据结构——图的概述
图是由顶点V集和边E集构成,因此图可以表示成 G = ( V , E ) G=(V,E) G=(V,E)。
图两种表示方法
1.邻接矩阵法;
无向图
邻接矩阵的特点为:主对角线一定为0且为对称矩阵
有向图
邻接矩阵是最容易理解的表示方法,优点是简单易懂,缺点是可能会占用很多存储空间,且没有任何意义,空间复杂度有向图是 O ( n 2 ) O(n^2) O(n2)
如果图的边不是很多,则不太适合用邻接矩阵表示。一般邻接矩阵适合表示稠密图,邻接表适合表示稀疏图。
2.邻接表法
无向图
有向图
邻接表就是更加灵活,节省存储空间,空间复杂度有向图是n+e(n:顶点数,e:边数),无向图是n+2e,但是表示起来复杂一些。
图的遍历(DFS和BFS)
详细图解看这里:
图的深度优先遍历(DFS)和广度优先遍历(BFS)算法分析
代码实现下一部分贴出
- 深度优先搜索DFS
深度优先搜索算法(英语:Depth-First-Search,DFS)是一种用于遍历或搜索树或图的算法。沿着树的深度遍历树的节点,尽可能深的搜索树的分支。当节点v的所在边都己被探寻过,搜索将回溯到发现节点v的那条边的起始节点。这一过程一直进行到已发现从源节点可达的所有节点为止。
如果还存在未被发现的节点,则选择其中一个作为源节点并重复以上过程,整个进程反复进行直到所有节点都被访问为止。属于盲目搜索。
显然,深度优先搜索是一个递归的过程。
深度优先遍历特点是,选定一个出发点后进行遍历,能前进则前进,若不能前进,回退一步再前进,或再回退一步后继续前进。依此重复,直到所有与选定点相通的所有顶点都被遍历。
图的深度优先搜索(Depth First Search),和树的先序遍历比较类似。
- 广度优先搜索BFS
广度优先搜索算法(Breadth First Search),又称为"宽度优先搜索"或"横向优先搜索",简称BFS。
它的思想是:从图中某顶点v出发,在访问了v之后依次访问v的各个未曾访问过的邻接点,然后分别从这些邻接点出发依次访问它们的邻接点,并使得“先被访问的顶点的邻接点先于后被访问的顶点的邻接点被访问,直至图中所有已被访问的顶点的邻接点都被访问到。
如果此时图中尚有顶点未被访问,则需要另选一个未曾被访问过的顶点作为新的起始点,重复上述过程,直至图中所有顶点都被访问到为止。
换句话说,广度优先搜索遍历图的过程是以v为起点,由近至远,依次访问和v有路径相通且路径长度为1,2…的顶点。
代码实现
无向图
- 邻接矩阵存储,无向有权图
/*
1.邻接矩阵存储,无向有权图
*/
#include<iostream>
#include<vector>
#include<stack>
#include<queue>
using namespace std;
class Graph{
private:
int V,E;
vector<vector<int>> adjMatrix;
vector<int> vertex;
vector<bool> visited;
public:
Graph(int n):V(n),adjMatrix(n,vector<int>(n)),vertex(n),visited(n,false){
for(int i=0;i<n;i++){
vertex[i]=i;
}
}
~Graph(){}
//添加边
void addEdge(int u,int v,int weight){
//插入新的边时,E++;
if(adjMatrix[u][v]==0)
E++;
adjMatrix[u][v]=weight;
adjMatrix[v][u]=weight;
}
//输出邻接矩阵
void printadjMatrix(){
for(auto& i:adjMatrix){
for(auto& j:i)
cout<<j<<" ";
cout<<endl;
}
}
//复位visited
void resetVisited(){
visited=vector<bool>(V,false);
}
//深度优先遍历
//递归实现
void dfs1(int v){
//输出顶点
cout<<vertex[v]<<" ";
//标记被访问过
visited[v]=true;
for(int j=0;j<V;j++){
if(adjMatrix[v][j]!=0&&!visited[j]){
dfs1(j);
}
}
}
//非递归实现
void dfs2(int v){
stack<int> stk;
cout<<vertex[v]<<" ";
visited[v]=true;
stk.push(v);
while(!stk.empty()){
int cur=stk.top();
int j=0;
for(;j<V;j++){
if(adjMatrix[cur][j]!=0&&!visited[j]){
cout<<vertex[j]<<" ";
visited[j]=true;
stk.push(j);
break;
}
}
if(j==V)
stk.pop();
}
}
//宽度优先遍历
void bfs(int v){
queue<int> que;
cout<<vertex[v]<<" ";
visited[v]=true;
que.push(v);
while(!que.empty()){
int cur=que.front();
que.pop();
for(int j=0;j<V;j++){
if(adjMatrix[cur][j]!=0&&!visited[j]){
//一定要在入队的时刻做输出和标记,不要在出队的时候做,那样会有好多重复元素入队
//可以看看leetcode上关于岛屿数量的题目
cout<<vertex[j]<<" ";
visited[j]=true;
que.push(j);
}
}
}
}
//求图的连通分量
int countComp(Graph graph){
int count=0;
graph.resetVisited();
for(int v=0;v<V;v++){
if(!visited[v]){
count++;
graph.dfs1(v);
}
}
return count;
}
};
int main(){
cout<<"please input number of vertex: ";
int V;
cin>>V;
Graph graph(V);
cout<<"please input number of edge: ";
int E;
cin>>E;
cout<<"please input edge:u v weight"<<endl;
int u,v,w;
for(int i=0;i<E;i++){
cin>>u>>v>>w;
graph.addEdge(u,v,w);
}
cout<<"adjMatrix: "<<endl;
graph.printadjMatrix();
cout<<"dfs1: ";
graph.dfs1(0);
graph.resetVisited();
cout<<endl<<"dfs2: ";
graph.dfs2(0);
graph.resetVisited();
cout<<endl<<"bfs: ";
graph.bfs(0);
cout<<endl;
system("pause");
return 0;
}
测试结果
- 邻接表存储,无向有权图
/*
2.邻接表存储,无向有权图
*/
class Graph{
private:
struct adjVertexNode{
int adjVertex;
int weight;
adjVertexNode* next;
adjVertexNode(int v,int w):adjVertex(v),weight(w),next(nullptr){}
};
struct vertexNode{
int vertex;//顶点
adjVertexNode* firstEdge;
vertexNode():firstEdge(nullptr){}
};
vector<vertexNode> adjList;
int V,E;
vector<bool> visited;
public:
Graph(int n):V(n),E(0),adjList(n),visited(n,false){
for(int i=0;i<n;i++){
adjList[i].vertex=i;
}
}
~Graph(){};
void addEdge(int u,int v,int weight){
adjVertexNode* newEdge=new adjVertexNode(v,weight);
//将新的边插到对应的邻接表位置
//链表的头插入法
newEdge->next=adjList[u].firstEdge;
adjList[u].firstEdge=newEdge;
//无向图,对称插入
newEdge=new adjVertexNode(u,weight);
newEdge->next=adjList[v].firstEdge;
adjList[v].firstEdge=newEdge;
}
//打印连接表存储的图
void printAdjList(){
for(int i=0;i<V;i++){
cout<<adjList[i].vertex;
adjVertexNode* cur=adjList[i].firstEdge;
while(cur){
cout<<"--("<<cur->weight<<")-->"<<cur->adjVertex;
cur=cur->next;
}
cout<<endl;
}
}
void resetVisited(){
visited=vector<bool>(V,false);
}
//深度优先遍历
//递归实现
void dfs1(int v){
cout<<adjList[v].vertex<<" ";
visited[v]=true;
adjVertexNode* cur=adjList[v].firstEdge;
while(cur){
if(!visited[cur->adjVertex]){
dfs1(cur->adjVertex);
}
cur=cur->next;
}
}
//非递归实现
void dfs2(int v){
stack<adjVertexNode*> stk;
cout<<adjList[v].vertex<<" ";
visited[v]=true;
stk.push(adjList[v].firstEdge);
while(!stk.empty()){
adjVertexNode* cur=stk.top();
while(cur){
if(!visited[cur->adjVertex]){
cout<<adjList[cur->adjVertex].vertex<<" ";
visited[cur->adjVertex]=true;
stk.push(adjList[cur->adjVertex].firstEdge);
break;
}
cur=cur->next;
}
if(!cur)
stk.pop();
}
}
//宽度优先遍历
void bfs(int v){
queue<adjVertexNode*> que;
cout<<adjList[v].vertex<<" ";
visited[v]=true;
que.push(adjList[v].firstEdge);
while(!que.empty()){
adjVertexNode* cur=que.front();
que.pop();
while(cur){
if(!visited[cur->adjVertex]){
cout<<cur->adjVertex<<" ";
visited[cur->adjVertex]=true;
que.push(adjList[cur->adjVertex].firstEdge);
}
cur=cur->next;
}
}
}
//求连通分量数
int countComp(Graph& graph){
int count=0;
for(int i=0;i<V;i++){
if(!visited[i]){
count++;
dfs1(i);
}
}
return count;
}
};
int main(){
cout<<"please input number of vertex: ";
int V;
cin>>V;
Graph graph(V);
cout<<"please input number of edge: ";
int E;
cin>>E;
cout<<"please input edge:u v weight"<<endl;
int u,v,w;
for(int i=0;i<E;i++){
cin>>u>>v>>w;
graph.addEdge(u,v,w);
}
cout<<"adjMatrix: "<<endl;
graph.printAdjList();
cout<<"dfs1: ";
graph.dfs1(0);
graph.resetVisited();
cout<<endl<<"dfs2: ";
graph.dfs2(0);
graph.resetVisited();
cout<<endl<<"bfs: ";
graph.bfs(0);
cout<<endl;
system("pause");
return 0;
}
有向图
关于有向图的代码实现,只需要改变上边代码中的addEdge函数即可,具体就不贴了