图的遍历的两种方法
广度优先(Breadth First Serrch)
像水波一样一层一层由内到外扩散访问
核心思想:先被访问的顶点,其邻接点也先被访问 ??
队列的思想:先进先出 notice:
深度优先(DFS)
一条路走到黑,如果走不通就返回一开始的位置走下一条路,
如果走得通就走出去了,其他的路也不管了
核心思想:后被访问(出栈)的顶点,其第一个邻接点先被访问(出栈) ??
栈的思想:后进先出、后出 notice:
eg:
b
↗ ↘
a → c 5个顶点,7条边
↓ ↙ ↓
e ← d
Vex[] = a b c d e a:0 b:1 c:2 d:3 e:4
顶(出发)点表 邻接点(进入点)表:逆序
[a|p_first] [4|p_next] [2|p_next] [1|p_next] NULL a→e a→c a→b
↓→↑ ↓→↑ ↓→↑ ↓→↑
[b|p_first] [2|p_next] NULL b→c
↓→↑ ↓→↑
[c|p_first] [4|p_next] [3|p_next] NULL c→e c→d
↓→↑ ↓→↑ ↓→↑
[d|p_first] [4|p_next] NULL d→e
↓→↑ ↓→↑
[e|p_first] NULL e→N
↓→↑
基于邻接矩阵的深度优先算法:
找到当前顶点的[没有被访问过]的[最近]邻接点入栈,「入栈的过程」 == 「深度优先遍历的过程」
从a走到e,发现走不通了e没用了出栈,返回到原点a,走另一条路a→c,
c→e,e已经被标记过了,走c→d,d入栈,d之后没有了,d出栈
c之后没有了,c出栈,走a→b,b入栈,b之后没有了,b出栈,a之后没有了,a出栈,栈空
入栈顺序:a、e、c、d、b 每一次入栈都要标记
| | | | | | | | | |←
| | | |← | | | |← | d |
| |← | e | | |← | c | | c |
|_a_| |_a_| |_a_| |_a_| |_a_|
| | | | | | | | | |
| |← | | | |← | | | |
| c | | |← | b | | |← | |
|_a_| |_a_| |_a_| |_a_| |___|←
过程总结:
step1 创建一个数组用于表示顶点是否被访问过
step2 创建一个空栈
step3 从图的起点(下标为0)出发,标记访问过,将顶点(下标)压入栈
step4 判断栈是否为空,若不为空则继续访问,否则结束
step5 顶点(头部)出栈后,依次访问该顶点[未被访问过的][最近邻接点],标记并入队列
step6 返回4
考虑非连通
b
↗ ↘
a → c f → g
↓ ↙ ↓
e ← d
#include <iostream>
#include "directed_list.h"
using namespace std;
int main()
{
struct ALG_Graph* d_graph;
d_graph = Create_ALG_Graph();
Show_ALG_Graph(d_graph);
cout << "深度优先搜索遍历邻接表:"<< endl;
// 只适用于连通图
DFS_ALG(d_graph);
// 适用于连通与非连通图
DFS_ALG_NotConnect(d_graph);
return 0;
}
directed_list.cpp
#include <iostream>
#include "directed_list.h"
#include "stack.h"
using namespace std;
struct ALG_Graph* Create_ALG_Graph(void)
{
int i,j;
char u, v;
struct ALG_Graph* graph;
// 申请内存空间,结构体多大申请多大,强制类型转换
graph = (struct ALG_Graph *)malloc(sizeof(struct ALG_Graph));
cout << "请输入顶点的个数: ";
cin >> graph->vex_num;
cout << "请输入边的个数: ";
cin >> graph->edge_num;
cout << "请输入顶点信息: " << endl;
for(i = 0; i < graph->vex_num; i++)
{
// 捕获每个顶点信息:a b c...
cin >> graph->Vex[i].node;
}
for(i = 0; i < graph->vex_num; i++)
{
// notice: 顶点表全部初始化为空指针
graph->Vex[i].first = NULL;
}
// 一条边对应一个邻接节点
while(graph->edge_num--)
{
cout << "请输入通过边连接起来的顶点:" << endl;
cin >> u;
cin >> v;
// 到graph中找到字符u,所对应的索引i(出发点的索引号)、j(进入点的索引号)
i = search_vex(graph, u);
j = search_vex(graph, v);
// 如果找到了
if(i != -1 && j != -1)
{
// notice: 构建邻接点表(形成单链表),入参:顶点表的顶点、出发点的索引号、进入点的索引号
create_adjNode_list(graph, i, j);
}
else// 如果没有找到
{
cout << "你输入的顶点信息是错的,请再次输入" << endl;
graph->edge_num++;
}
}
return graph;
}
int search_vex(struct ALG_Graph* graph, char c)
{
int i;
// 有多少个顶点循环多少次
for(i = 0; i < graph->vex_num; i++)
{
if(c == graph->Vex[i].node)
return i;
}
return -1;
}
/*
输入前:
[a|p_first] NULL
↓→↑
输入 i j
a b 0 1 [a|p_first] [1|p_next] NULL
↓→↑ ↓→↑
a c 0 2 [a|p_first] [2|p_next] [1|p_next] NULL
↓→↑ ↓→↑ ↓→↑
a e 0 4 [a|p_first] [4|p_next] [2|p_next] [1|p_next] NULL
↓→↑ ↓→↑ ↓→↑ ↓→↑
最终:
顶点表 邻接点表
[a|p_first] [4|p_next] [2|p_next] [1|p_next] NULL
↓→↑ ↓→↑ ↓→↑ ↓→↑
*/
// 入参:顶点表的顶点、出发点的索引号、进入点的索引号 i --> j
void create_adjNode_list(struct ALG_Graph* graph, int i, int j)
{
// 创建邻接节点
struct AdjNode* s = (struct AdjNode*)malloc(sizeof(struct AdjNode));
// 邻接点的下标是进入点索引
s->index = j;
// [1|next] NULL [2|p_next] [1|p_next] NULL
// ↓→↑ ↓→↑ ↓→↑
s->next = graph->Vex[i].first;
/*
↑→ [2|p_next] →↓
[a|first] [1|next] NULL
↓→ → → X → → → ↑ ↓→↑
*/
// [a|first] [1|next] NULL [a|p_first] [2|p_next] [1|p_next] NULL
// ↓→↑ ↓→↑ ↓→↑ ↓→↑ ↓→↑
graph->Vex[i].first = s;
}
void Show_ALG_Graph(struct ALG_Graph * graph)
{
int i,j;
// 邻接点指针
struct AdjNode* temp;
cout << "顶点表\t邻接点表"<< endl;
for(i = 0; i < graph->vex_num; i++)
{
// 顶点信息
cout << graph->Vex[i].node << "\t";
temp = graph->Vex[i].first;
// 显示单链
while(temp != NULL)
{
// 邻接表索引
cout << " " << temp->index;
// 更新
temp = temp->next;
}
cout << endl;
}
}
// 深度优先遍历邻接表,连通
void DFS_ALG(struct ALG_Graph* graph)
{
int u,n;
// 邻接点表中的邻接点
struct AdjNode* p;
// step1 创建一个数组用于表示顶点是否被访问过
int visited[MAX] = {0};
// step3 从图的起点(下标为0)出发,标记访问过,将顶点(下标)入队列
visited[0] = 1;
push(0);
// 「入栈的过程」 == 「深度优先遍历的过程」
cout << graph->Vex[0].node;
// step4 判断如果队列是否为空,若是则继续访问,否则结束
while(!isempty())
{
u = stack[top-1];
p = graph->Vex[u].first;// a的第一个邻接点e
// step5 顶点(头部)出队列后,判断p_first是否为空,如果不为空则继续
// 依次访问该顶点的 [未被访问过的][最近邻接点],如果未被标记并入队列
while(p)
{
n = p->index;
// 如果当前顶点没有被标记过
if(visited[n] == 0)
{
visited[n] = 1;
// 下标入栈
push(n);
//「入栈的过程」 == 「深度优先遍历的过程」
cout << " "<< graph->Vex[n].node;// e、c
// 更新为当前节点的最近邻接点
p = graph->Vex[n].first;
}
else// 如果当前顶点被标记过
p = p->next;// 指向c
}
// 一条路走不桶,则出栈
pop();
}
cout << endl;
}
// 深度优先遍历邻接表,非连通
void DFS_ALG_NotConnect(struct ALG_Graph* graph)
{
int u,n;
// 邻接点表中的邻接点
struct AdjNode* p;
// step1 创建一个数组用于表示顶点是否被访问过
int visited[MAX] = {0};
// 防止非连通
for(int i = 0; i < graph->vex_num; i++)
{
// 如果该顶点没有被访问过则
if(visited[i] == 0)
{
// 起始点
cout << graph->Vex[i].node;// a、....f
// step3 从图的起点(下标为0)出发,标记访问过,将顶点(下标)入队列
visited[i] = 1;
push(i);
// step4 判断如果队列是否为空,若是则继续访问,否则结束
while(!isempty())
{
// step5 顶点(头部)出队列后,判断p_first是否为空,如果不为空则继续
// 依次访问该顶点的 [未被访问过的][邻接点],如果未被标记并入队列
u = stack[top-1];
p = graph->Vex[u].first;// a的第一个邻接点e
// 若顶点的邻接点不为空
while(p)
{
n = p->index;
// 如果没有被标记过
if(visited[n] == 0)
{
visited[n] = 1;
// 下标入栈
push(n);
cout << " "<< graph->Vex[n].node;// e、c
p = graph->Vex[n].first;
}
else
// 更新:从a->e,更新为a->c
p = p->next;// 指向c
}
// 一条路走不桶,则出栈
pop();
}
}
}
cout << endl;
}
directed_list.h
#ifndef __directed_list_h__
#define __directed_list_h__
#define MAX 100
// 邻接点表(链表)中的邻接点
struct AdjNode
{
// [邻接点的下标|指向下一个邻接点的指针(逆序)]
int index;
struct AdjNode* next;
};
// 顶点表(链表)中的顶点
struct VexNode
{
// [顶点的信息|该顶点指向第一个邻接点的指针(逆序)]
char node;
struct AdjNode* first;
};
struct ALG_Graph
{
// 顶点的个数,边的个数
int vex_num, edge_num;
// 一维数组储存顶点的信息 --> 顶点表
// 二维矩阵储存顶点和顶点之间的邻接关系 --> 邻接点表
// 数组中的每个元素都是一个结构体对象(一个顶点的单链)
struct VexNode Vex[MAX];
};
struct ALG_Graph* Create_ALG_Graph(void);
int search_vex(struct ALG_Graph* graph, char c);
void create_adjNode_list(struct ALG_Graph* graph, int i, int j);
void Show_ALG_Graph(struct ALG_Graph * graph);
void DFS_ALG(struct ALG_Graph* graph);
void DFS_ALG_NotConnect(struct ALG_Graph* graph);
#endif