一、图的基本概念
二、图的存储结构
因为图中既有节点,又有边(节点与节点之间的关系),因此,在图的存储中,只需要保存:节点和边关系即可。节点保存比较简单,只需要一段连续空间即可。
其边关系一般采用邻接矩阵或邻接表的方式保存。
2.1. 邻接矩阵
因为节点与节点之间的关系就是连通与否,即为0或者1,因此邻接矩阵(二维数组)即是:先用一个数组将定点保存,然后采用矩阵来表示节点与节点之间的关系。
注意:
- 无向图的邻接矩阵是对称的,第i行(列)元素之和,就是顶点i的度。有向图的邻接矩阵则不一定是对称的,第i行(列)元素之后就是顶点i 的出(入)度。
- 如果边带有权值,并且两个节点之间是连通的,上图中的边的关系就用权值代替,如果两个顶点不通,则使用无穷大代替。
- 邻接矩阵的优势是快速找到两个顶点之间的边,适合于边比较多的图;其劣势为要找一个顶点连出去的边,需要遍历其他顶点,因此时间复杂度为O(N),并且如果顶点比较多,边比较少时,矩阵中存储了大量的0成为系数矩阵,比较浪费空间。
2.2. 邻接表
邻接表:使用数组表示顶点的集合,使用链表表示边的关系。
- 无向图邻接表存储
注意:无向图中同一条边在邻接表中出现了两次。如果想知道顶点vi的度,只需要知道顶点vi边链表集合中结点的数目即可。
- 有向图邻接表存储
邻接表的优势为:
- 找一个点相连的顶点的边很快。
- 适合边比较少,比较稀疏的图。
劣势为:
要确认两个顶点是否相连为O(N),因为要把所有的边都找一遍。
三、图的广度优先遍历(BFS)
四、图的深度优先遍历(DFS)
五、代码实现(邻接矩阵和邻接表)
#include<iostream>
#include<vector>
#include<map>
#include<queue>
using namespace std;
//邻接矩阵
namespace Matrix
{
//Direction表示是否为有向图,V表示中节点的属性,W表示边的权值
template<class V, class W,bool Direction=false>
class Graph
{
public:
Graph(const V* vertexs, size_t n)
{
_vertexs.reserve(n);
for (size_t i = 0; i < n; i++)
{
_vertexs.push_back(vertexs[i]);
_VIndexMap[vertexs[i]] = i;
}
_matrix.resize(n);
for (auto& e : _matrix)
{
e.resize(n,W());
}
}
//获取顶点的下标
size_t GetVertexIndex(const V& v)
{
auto ret = _VIndexMap.find(v);
if (ret != _VIndexMap.end())
{
return ret->second;
}
else
{
throw invalid_argument("不存在的顶点");
return -1;
}
}
void AddEdge(const V& src, const V& dst,const W&w)
{
size_t srcindex = GetVertexIndex(src);
size_t dstindex = GetVertexIndex(dst);
_matrix[srcindex][dstindex] = w;
if (Direction == false)
{
//无向图
_matrix[dstindex][srcindex] = w;
}
}
//广度优先搜索
void BFS(const V& src)
{
//找到其下标
size_t srcindex = GetVertexIndex(src);
//visited表示没有被访问过的顶点
vector<bool>visited;
visited.resize(_vertexs.size(), false);
//使用队列完成广度遍历
queue<int>q;
q.push(srcindex);
visited[srcindex] = true;
size_t d = 1;
//dSize保证队列中的数据走完
size_t dSize = 1;
while (!q.empty())
{
printf("%s的%d度好友:", src.c_str(), d);
while (dSize--)
{
size_t front = q.front();
q.pop();
for (size_t i = 0; i < _vertexs.size(); ++i)
{
if (visited[i] == false && _matrix[front][i] != W())
{
printf("[%d:%s]", i, _vertexs[i].c_str());
q.push(i);
visited[i] = true;
}
}
}
cout << endl;
dSize = q.size();
++d;
}
}
//深度优先搜索
void _DFS(size_t srcIndex, vector<bool>& visited)
{
printf("[%d:%s]", srcIndex, _vertexs[srcIndex].c_str());
visited[srcIndex] = true;
for (size_t i = 0; i < _vertexs.size(); ++i)
{
if (visited[i] == false&&_matrix[srcIndex][i]!=W())
{
_DFS(i, visited);
}
}
}
void DFS(const V& src)
{
//找到其下标
size_t srcindex = GetVertexIndex(src);
//visited表示没有被访问过的顶点
vector<bool>visited;
visited.resize(_vertexs.size(), false);
_DFS(srcindex, visited);
}
private:
map<V, int> _VIndexMap;//顶点和下标的映射关系
vector<V> _vertexs;//顶点集合
vector<vector<W>> _matrix;//存储边集合的矩阵
};
void TestGraph()
{
string a[] = { "张三","李四","王五","赵六","周七"};
Graph<string, int>g1(a,sizeof(a)/sizeof(a[0]));
g1.AddEdge("张三","李四",100);
g1.AddEdge("张三","王五",200);
g1.AddEdge("王五","赵六",10);
g1.AddEdge("李四","周七",50);
g1.BFS("张三");
g1.DFS("张三");
}
}
//邻接表
namespace LinkTable
{
template<class W>
struct LinkEdge
{
int _srcIndex;
int _dstIndex;
W _w;
LinkEdge<W>* _next;
LinkEdge(const W& w)
:_srcIndex(-1)
, _dstIndex(-1)
, _w(w)
, _next(nullptr)
{}
};
//Direction表示是否为有向图,V表示中节点的属性,W表示边的权值
template<class V, class W, bool Direction = false>
class Graph
{
typedef LinkEdge<W> Edge;
public:
Graph(const V* vertexs, size_t n)
{
_vertexs.reserve(n);
for (size_t i = 0; i < n; i++)
{
_vertexs.push_back(vertexs[i]);
_VIndexMap[vertexs[i]] = i;
}
_linkTable.resize(n,nullptr);
}
size_t GetVertexIndex(const V& v)
{
auto ret = _VIndexMap.find(v);
if (ret != _VIndexMap.end())
{
return ret->second;
}
else
{
throw invalid_argument("不存在的顶点");
return -1;
}
}
void AddEdge(const V& src, const V& dst, const W& w)
{
size_t srcindex = GetVertexIndex(src);
size_t dstindex = GetVertexIndex(dst);
Edge* sd_edge = new Edge(w);
sd_edge->_srcIndex = srcindex;
sd_edge->_dstIndex = dstindex;
sd_edge->_next = _linkTable[srcindex];
_linkTable[srcindex]=sd_edge;
if (Direction == false)
{
//有向图的起点和终点相互颠倒
Edge* ds_edge = new Edge(w);
ds_edge->_srcIndex = dstindex;
ds_edge->_dstIndex = srcindex;
ds_edge->_next = _linkTable[dstindex];
_linkTable[dstindex] = ds_edge;
}
}
void BFS(const V& src)
{
//找到其下标
size_t srcindex = GetVertexIndex(src);
//visited表示没有被访问过的顶点
vector<bool>visited;
visited.resize(_vertexs.size(), false);
//使用队列完成广度遍历
queue<int>q;
q.push(srcindex);
//visited[srcindex] = true;
size_t d = 0;
//dSize保证队列中的数据走完
size_t dSize = 1;
while (!q.empty())
{
printf("%s的%d度好友:", src.c_str(), d);
while (dSize--)
{
size_t index = q.front();
q.pop();
if (!visited[index])
{
cout << _vertexs[index] << " ";
visited[index] = true;
Edge* pCur = _linkTable[index];
while (pCur)
{
q.push(pCur->_dstIndex);
pCur = pCur->_next;
}
}
}
cout << endl;
dSize = q.size();
++d;
}
}
void _DFS(int index, vector<bool>& visited) {
if (!visited[index])
{
cout << _vertexs[index] << " ";
visited[index] = true;
Edge* pCur = _linkTable[index];
while (pCur)
{
_DFS(pCur->_dstIndex, visited);
pCur = pCur->_next;
}
}
}
void DFS(const V& v) {
vector<bool> visited(_vertexs.size(), false);
_DFS(GetVertexIndex(v), visited);
for (size_t index = 0; index < _vertexs.size(); ++index)
{
_DFS(index, visited);
}
cout << endl;
}
//非递归深度优先遍历
void DFS_non_recursive(const V& v) {
size_t index = GetVertexIndex(v);
stack<size_t>st;
set<size_t>set;
st.push(index);
set.insert(index);
cout << _vertexs[index] << " ";
while (!st.empty())
{
Edge* cur = _linkTable[st.top()];
st.pop();
while (cur)
{
if (!set.count(cur->_dstIndex))
{
st.push(cur->_srcIndex);
st.push(cur->_dstIndex);
set.insert(cur->_dstIndex);
cout << _vertexs[cur->_dstIndex] << " ";
cur = cur->_next;
break;
}
cur = cur->_next;
}
}
}
private:
map<V, int> _VIndexMap;//顶点和下标的映射关系
vector<V> _vertexs;//顶点集合
vector<Edge*> _linkTable;//边的集合的邻接表
};
void TestGraph()
{
string a[] = { "张三","李四","王五","赵六","周七" };
Graph<string, int>g1(a, sizeof(a) / sizeof(a[0]));
g1.AddEdge("张三", "李四", 100);
g1.AddEdge("张三", "王五", 200);
g1.AddEdge("王五", "赵六", 10);
g1.AddEdge("李四", "周七", 50);
g1.BFS("张三");
g1.DFS("张三");
}
}