图的存储
图有两种存储方式:邻接表和邻接矩阵。邻接矩阵需要的空间较大,一般适用于顶点数目不超过1000的情况
邻接矩阵
相当于一个离散的平面直角坐标系,每一个坐标代表一对节点的相互之间的关系,当两节点不直接相连的时候,这个坐标上的值可以设为0、-1或者是无穷大。否则可以在该点上赋值为这两点之间的权值。
邻接表
将这些节点之间的关系保存在几个链表中,每一个链表都是在描述一个节点的连接情况。可以将这些数据保存在向量中。
const int N = 10;//顶点个数
vector<int>Adj[N];
//之后直接将节点的信息连接到相应的向量之后
int x;
void Create() {
for (int i = 0; i <= 10; ++i) {
Adj[i].push_back(x);
}
}
//当需要存放更多节点信息的时候,可以用结构体+构造函数
struct Node
{
int v, w;
Node(int _v, int _w) :v(_v), w(_w) {};
};
//赋值:Adj[1].push_back(Node(3,4));
图的遍历
遍历就是将图中所有的节点都访问一遍,要做到不重复访问,不遗漏。主要的方法有深度优先和广度优先。
深度优先
访问的时候优先访问下一层的节点,运用递归的话,在递归返回的时候就相当于一次回溯,然后继续递归,即向下访问。
const int MAXV = 1000;//最大顶点数
const int INF = 100000000000;//一个很大的数字
//邻接矩阵:检查其邻接节点是否存在然后逐一访问。
class Adjacency_matrix {
private:
int n, G[MAXV][MAXV];//n为顶点数
bool vis[MAXV] = { false };//访问数组,没访问的数组赋值为false
void DFS(int u, int depth) {
vis[u] = true;
//将该节点的所有相邻的节点全部访问一遍
for (int v = 0; v < n; ++n) {
//该节点未被访问且与访问节点相联
if (vis[v] == false && G[u][v] != INF)DFS(v, depth + 1);
}
}
//确保所有连通子图都被访问到
void DFSTrave() {
for (int u = 0; u < n; ++u) {
if (vis[u] == false)DFS(u, 1);
}
}
};
//邻接表
class Adjancency_list {
private:
vector<int>Adh[MAXV];
int n;
bool vis[MAXV] = { false };
void DFS(int u, int depth) {
vis[u] = true;
//Traveling node u's all neighbours
for (int i = 0; i < Adj[u].size(); ++i) {
int v = Adj[u][i];//u节点的第i+1的邻接节点
if (vis[v] == false)DFS(v, depth + 1);//没有访问则向下访问
}
}
void DFSTrave() {
//保证所有连通子图的节点都被访问到
for(int u = 0; u < n; ++u) {
if (vis[u] == false)DFS(u, 1);
}
}
};
广度优先
在遍历过程中优先访问一个节点的所有的相邻的节点。这个时候需要借助队列,将该节点的所有相连节点加入到队列中,在队列中的节点顺序就是最终的广度优先遍历的顺序。同时,为了防止重复访问,需要建立一个数组,将节点的序号作为数组的下标,作为该节点是否被访问的标识。
算法思想:先将起始节点加入到队列中,然后不断取出队列中的元素,访问并加入到队列中,并将该点相连的节点加入到队列中,直到队列中的元素为空。
判断一个节点v1是否与另一个节点v2是否相连:
邻接矩阵中,只需要确定[v1,v2]所对应的值是否为1,是则为相连,反之不相连。
在邻接表中,只需要遍历这个节点所连接的那一条邻接表即可。
int n, G[MAXV][MAXV];
bool inq[MAXV] = { false };
//广度优先邻接矩阵版:先将首个节点入队,
class BFS_AdjMatrix {
public:
void BFS(int u) {
queue<int>q;
//将首节点放到队列中
q.push(u);
inq[u] = true;
//将相邻的节点加入到队列中
while (!q.empty()) {
int u = q.front();
q.pop();
for (int v = 0; v < n; ++v) {
if (inq[v] == false && G[u][v] != INF) {
q.push(v);
inq[v] = true;//访问之后加入队列,改变标识
}
}
}
}
void BFSTrave() {
//将未被访问过的节点全部访问一遍
for (int u = 0; u < n; ++u) {
if (inq[u] == false)BFS(u);
}
}
};
//邻接表:访问节点的方式为遍历该向量的所有节点
class BFS_AdjList {
private:
vector<int>Adj[MAXV];//这其实相当于一个二维向量
int n;
bool inq[MAXV] = { false };//判断节点是否在队列中
void BFS(int u) {
queue<int>q;
q.push(u);
inq[u] = true;
while (!q.empty())
{
int u = q.front();
q.pop();
for (int i = 0; i < Adj[u].size(); ++i) {
int v = Adj[u][i];
if (inq[v] == false) {
q.push(v);
inq[v] = true;
}
}
}
}
void BFSTravel() {
for (int u = 0; u < n; ++u) {
if (inq[u] == false)BFS(u);
}
}
//当想要得出所有节点的所在层的信息的时候:利用结构体,在其中加上层次这一变量
struct Node
{
int v;//节点编号
int layer;//所在层次,头节点为第0层
};
void BFS(int s) {
vector<Node>Adj[MAXV];
int n;
bool inq[MAXV] = { false };
queue<Node>q;
Node start;//第一个访问的节点
//初始化首个节点
start.v = s;
start.layer = 0;
q.push(start);
inq[start.v] = true;//做好相应的标识
while (!q.empty())
{
Node topNode = q.front();
q.pop();
int u = topNode.v;
for (int i = 0; i < Adj[u].size(); ++i) {
//将next赋值为该节点连接的节点
Node next = Adj[u][i];
next.layer = topNode.layer + 1;//赋值为上一层的层数+1
if (inq[next.v] == false) {
q.push(next);
inq[next.v] = true;
}
}
}
}
};
最短路径
这类问题是求解图中从源点到图中任一节点的最短的距离(路径)。主要的算法有Dijkstra算法、Bellman-Ford算法、SPFA算法、Floyd算法。
Dijkstra算法
算法图示(取自Wikipedia)
//Dijkstra算法
const int MAXV = 1000;
const int INF = 1e9;//无穷大
//寻找最短距离
//邻接矩阵:先找到与源点最近的相邻节点,然后从该节点出发,遍历该节点的所有的未被访问的相邻的节点,并更新最小权值
//O(V*(V+V)) = O(V^2)
class SPD_AdjMatrix {
private:
int n, G[MAXV][MAXV];//分别为顶点数和以邻接矩阵形式存储的图
int d[MAXV];//该节点到源点的最短距离
bool vis[MAXV] = { false };//访问数组,以图节点的编号作为索引
void Dijkstra(int s){
fill(d, d + MAXV, INF);//将所有的节点初始化为极大值
d[s] = 0;//源点到源点的距离为0
//找到与起点相邻的路径最短的节点
int u = -1, MIN = INF;//最短距离的节点编号和距离
for (int i = 0; i < n; ++i) {
//遍历所有相邻节点
for (int j = 0; j < n; ++j) {
//未被访问,并且距离权值小于之前记录的最小值
if (vis[j] == false && d[j] < MIN) {
u = j;
MIN = d[j];
}
}
}
//说明剩下的顶点与起点s不相连通
if (u == -1)return;
vis[u] = true;
for (int v = 0; v < n; ++v) {
//与该节点相连且未被访问且以该节点为中介的权值比原先的权值更小
if (vis[v] == false && G[u][v] != INF && d[u] + G[u][v] < d[v]) {
d[v] = d[u] + G[u][v];
}
}
}
//要记录最短路径的具体情况,可以记录每个节点最短路径的前驱,将该节点作为数组下标,数组中的值作为前驱节点的编号
int pre[MAXV];
void DijkstraPath(int s) {
fill(d, d + MAXV, INF);
for (int i = 0; i < n; ++i)pre[i] = i;//(添加)初始化,将每个节点的前驱初试化为其本身
d[s] = 0;
//找到与起点相邻的路径最短的节点
int u = -1, MIN = INF;
for (int i = 0; i < n; ++i) {
for (int j = 0; j < n; ++j) {
if (vis[j] == false && d[j] < MIN) {
u = j;
MIN = d[j];
}
}
}
if (u == -1)return;
vis[u] = true;
for (int v = 0; v < n; ++v) {
if (vis[v] == false && G[u][v] != INF && d[u] + G[u][v] < d[v]) {
d[v] = d[u] + G[u][v];
pre[v] = u;//(添加)将节点前驱初始化
}
}
}
//输出路径
void DFS(int s, int v) {
if (v == s) {
printf("%d\n", s);
return;
}
DFS(s, pre[v]);
printf("%d\n", v);
}
};
//邻接表:O(V^2 + E)
class SPD_AdjList {
private:
struct Node{
int v, dis;
};
vector<Node>Adj[MAXV];
int n;
int d[MAXV];
bool vis[MAXV] = { false };
void Dijkstra(int s) {
fill(d, d + MAXV, INF);
d[s] = 0;
for (int i = 0; i < n; ++i) {
int u = -1, MIN = INF;
for (int j = 0; j < n; ++j) {
if (vis[j] == false && d[j] < MIN) {
u = j;
MIN = d[j];
}
}
if (u == -1)return;
vis[u] = true;
//利用向量的特性,直接获取相邻的节点
for (int j = 0; j < Adj[u].size(); ++j) {
int v = Adj[u][j].v;
//检查是否满足条件
if (vis[v] == false && d[u] + Adj[u][j].dis < d[v]) {
d[v] = d[u] + Adj[u][j].dis;
}
}
}
}
};
To Be Continue…