一、图的深度优先遍历
原则
一直向深处遍历,直到到头就返回到上一节点继续向深处遍历。
举例
基本原理
选定起点后,打印起点,for循环在邻接矩阵里暴力查找边存在并且终点还未被走到的边,用终点作为下次递归的起点。
代码实现
void _DFS(int srci, const vector<bool>& visited)
{
cout << srci << "->" << _vertexs[srci] << endl;
visited[srci] = true;
for(int i = 0; i < _vertexs.size(); i++)
{
if(_matrix[srci][i] != INT_MAX && visited[i] == false)
{
_DFS(i, visited);
}
}
}
void DFS(const V& src)
{
int srci = GetIndex(src);
// 记录走过的顶点
vector<bool> visited(_vertexs.size(), false);
_DFS(srci, visited);
}
二、图的广度优先遍历
原则
找到起点的一度顶点,二度顶点,三度顶点······
举例
基本原理
先入队起点,若队列不为空进行循环,循环中先拿到队头顶点,然后打印出队,再用for循环在邻接矩阵中找到以队头顶点为起点的边,要求边的终点未遍历到,将终点入队。此时我们就拿到了对头顶点的一度顶点。直至循环结束即可。
代码实现
void BFS(const V& src)
{
int n = _vertexs.size();
int srci = GetIndex(src);
queue<int> q;
q.push(srci);
vector<bool> visited(n, false);
visited[srci] = true;
while(!q.empty())
{
int front = q.front();
cout << front << "->" << _vertexs[front] << endl;
q.pop();
for(int i = 0; i < n; i++)
{
if(_matrix[front][i] != INT_MAX && visited[i] == false)
{
q,push(i);
visited[i] = true;
}
}
}
}
三、广度优先遍历进阶
如何实现一次打印一层节点?
如上图我们是否能实现:
A
B C D
E F
G H
I
基本原理
我们可以用levelsize来记录每一层有几个节点,出队时可以循环levelsize次。
代码实现:
void BFSLevel(cinst V& src)
{
int front;
int n = _vertexs.size();
int srci = GetIndex(src);
int levelsize = 1;
vector<bool> visited(n, false);
queue<int> q;
q.push(srci);
visited[srci] = true;
while(!q.empty())
{
while(levelsize)
{
front = q.front();
q.pop();
// 出队时要减一
levelsize--;
cout << front << "->" << _vertexs[front] << " ";
}
cout <<endl;
for (int i = 0; i < n; i++)
{
if (_matrix[front][i] != INT_MAX)
{
if (visited[i] == false)
{
q.push(i);
visited[i] = true;
levelsize++;
}
}
}
}
}
四、最小生成树算法
1、Kruskal算法(一种从全局考虑的算法)
原则
每次都挑最小的边进行插入,但是不能产生环,因为一旦有环那就不是用最少的边把n个顶点连接起来,就不是生成树.。
基本原理
只要我们每次都挑的是全部边中权值最小并且不会构成环的边,那循环结束挑到的n-1条边就是我们需要的。
注意几个问题
1、如何判断一条边加进去之后是否会变成环
我们可以用并查集来做检查。每次加边进去的时候我们就把起点终点并到一个集合中,当我们准备加边之前可以先判断起点终点是否在一个集合中。
2、如何高效的取到最小的边
可以借助stl中的优先级队列priority_queue,但是要注意因为我们要判断边的权值大小,而边是自定义类型,判断大小我们要在类里面重载operator<()。还要注意挑小的边要用greater,每次把大的边压到堆底。
代码实现
// 由于我们要频繁用到边,但是在邻接矩阵中边的遍历太麻烦,所以我们在写一个边
struct Edge
{
int _srci;
int _dsti;
W _w;
Edge(int srci, int dsti, W w)
:_srci(srci)
, _dsti(dsti)
, _w(w)
{}
//要让大的数在后面
bool operator>(const Edge& e) const
{
return _w > e._w;
}
};
W Kruskal(Self& minTree)
{
int n = _vertexs.size();
minTree._indexmap = _indexmap;
minTree._vertexs = _vertexs;
minTree._matrix.resize(n);
for (int i = 0; i < n; i++)
{
minTree._matrix[i].resize(n, MAX_W);
}
//优先级队列,里面需要元素类型,选用结构存储,仿函数比较大小(自定义类型要自己写仿函数)
priority_queue<Edge, vector<Edge>, greater<Edge>> minq;
//最小生成树只有n-1条边
int size = n - 1;
//先把所有的边加到队列
for (int i = 0; i < n; i++)
{
//由于是无向图,只要看一半
for (int j = 0; j < i; j++)
{
if (_matrix[i][j] != INT_MAX)
{
minq.push(Edge(i, j, _matrix[i][j]));
}
}
}
//需要判环 所以要用到并查集
UnionFindSet ufs(n);
W totalW = W();
while (!minq.empty())
{
Edge min = minq.top();
minq.pop();
//先判环
if (!ufs.InSet(min._dsti, min._srci))
{
cout << _vertexs[min._srci] << "->" << _vertexs[min._dsti] << ":" << min._w << endl;
minTree.AddEdge(min._srci, min._dsti, min._w);
ufs.Union(min._srci, min._dsti);
totalW += min._w;
size--;
}
}
//循环走完
if (size == 0)
{
return totalW;
}
//如果没找到就要返回默认值
else
{
return W():
}
}
代码解释
为了方便我加了typedef Graph<V, W, MAX_W, Direction> Self
创建minTree,其中顶点集合_vertexs和_indexmap不变,minTree自己的邻接矩阵要通过算法得到,所以只要初始化成最大值就行。
创建优先级队列,并把所有边放入队列。
用while循环, 先找到最小边,并查集检查起点终点是否在同一集合,不同就minTree添加边。不要忘记并查集合并两个顶点。
若找到n-1条边就返回totalW,没有就返回默认值。
2、Prim算法(一种从起点考虑的算法)
基本原理
设走过的顶点在集合X中,未走过的再Y中,在XY中分别挑一个点使构成的边权值是最小的加入到最小生成树中,重复n-1次。当然加入的边的终点要从Y变到X。
代码实现
struct Edge
{
int _srci;
int _dsti;
W _w;
Edge(int srci, int dsti, W w)
:_srci(srci)
, _dsti(dsti)
, _w(w)
{}
//要让大的数在后面
bool operator>(const Edge& e) const
{
return _w > e._w;
}
};
W Prim(Self& minTree, const V& src)
{
int n = _xertexs.size();
int srci = GetIndex(src);
minTree._indexmap = _indexmap;
minTree._vertexs = _vertexs;
minTree._matrix.resize(n);
for (int i == 0; i < n; i++)
{
minTree._matrix[i].resize(n, MAX_W);
}
W totalW = W();
//创建一个集合标记走过的顶点
vector<bool> x(n, false);
x[srci] = true;
priority_queue<Edge, vector<Edge>, greater<Edge>> minq;
for (int i = 0; i < n; i++)
{
if (_matrix[srci][i] != MAX_W)
{
minq.push(Edge(srci, i, _matrix[srci][i]));
}
}
int size = 0;
while (!minq.empty())
{
Edge min = minq.top();
minq.pop();
//起点一定在x,只要终点在x就构成环
if (X[min.dsi])
{
cout << "构成环" << endl;
}
else
{
minTree.AddEdge(min._srci, min._dsti, min._w);
X[min._dsti] = true;
size++;
totalW += min._w;
if (size == n - 1)
break;
for (int i = 0; i < n; i++)
{
if (_matrix[dsti][i] != MAX_W && X[i] == false)
{
minq.push(Edge(dsti, i, _matrix[dsti][i]));
}
}
}
}
if (size == n - 1)
return totalW;
else
return W():
}
代码解释
创建minTree,其中顶点集合_vertexs和_indexmap不变,minTree自己的邻接矩阵要通过算法得到,所以只要初始化成最大值就行。
创建X集合来标记走过的顶点,先把srci存在X中。
创建优先级队列,在while循环开始之前先把以srci为起点的边入队,由于一开始只有srci是走过的顶点,所以可以不要判断X[i]==false。
找到队列中最小边min,若min的终点在X中就表示min的起点与终点都在X,构成环。若不在就先minTree入边,并把min的终点加到X集合。
此时我们要把min的终点dsti看成起点,用for循环选出以dsti为起点的边,注意此时要判断X[i]==false,我们不能让dsti->i这条边的终点是已经走过的顶点入队。
最后在while中特判是否已经选到n-1条边。
若找到n-1条边就返回totalW,没有就返回默认值。