图的概念:图是一种非线性结构,由顶点即顶点的关系构成的一种数据结构;图的应用场景有:地图、用来描述社交关系、在网络中的路由选择。
图的基本知识:
图可以按照顶点间的边的方向分为:有向图 、 无向图;
完全图 :在由n个结点构成的无向图中,若有(N(N-1))/2 条边,则成为完全图;
权重:在一些图中,边具有与之相关的数值,称为权值;(权值可表示不同的意义,比如两顶点间的时间/距离等);
度 :与某个结点相关联的边的数目,则称为这个顶点的度;
连通图:在无向图中V1到V2有路径(V1,V2表示的是顶点),则成V1和V2是连通的;若一个图中任意两个顶点都是连通的,则称此图是连通图;
强连通图:在有向图中,任意两个结点都是连通的,则称此图是强联通图;
生成树:一个无向连通图的生成树是它的极小连通子图(若图中有N个结点,则生成树(不构成回路)由N-1条边构成);
最小生成树:生成树中,N-1条边,权值和最小的生成树;
图的存储:
临接矩阵,临接表;
图的遍历:
深度优先遍历,广度优先遍历;
分别用图的两种存储方式来实现图:
#pragma once //临接矩阵存储图
#include<map>
#include<vector>
#include<string>
template<class V,class W,bool digraph = false> //digraph 有向
class GraphMatrix
{
public:
GraphMatrix(V* vertexs, size_t n)
{
_vertexs.resize(n);
for(size_t i = 0 ;i < n ;i++)
{
_vertexs[i] = vertexs[i];
_indexMap[vertexs[i]] = i;
}
for(size_t i = 0 ; i < n;i++)
{
_matrix.resize(n);
for(size_t j = 0 ;j < n ;j++)
{
_matrix[i].resize(n,0);
}
}
}
size_t GetVertexIndex(const V& v)
{
return _indexMap[v];
}
void AddEdge(const V& src, const V& dst, const W& w)
{
//if(_indexMap.count(src) == 0 ) ???当所插入的顶点不在临接矩阵时
//{
// _vertexs.push_back(src);
// _indexMap[src] = _vertexs.size()-1;
//
//}
//if(_indexMap.count(dst) == 0 )
//{
// _vertexs.push_back(dst);
// size_t i = _vertexs.size();
// _indexMap[dst] = _vertexs.size()-1;
//}
size_t srcIndex = GetVertexIndex(src);
size_t dstIndex = GetVertexIndex(dst);
_matrix[srcIndex][dstIndex] = w;
if(digraph == false)
{
_matrix[dstIndex][srcIndex] = w;
}
}
protected:
map<V,size_t> _indexMap;
vector<V> _vertexs; // 顶点集合
vector<vector<W>> _matrix; // 临接矩阵
};
#pragma once
#include<queue>
template<class W>
struct LinkEdge//边
{
W _w ;
size_t _srcIndex;
size_t _dstIndex;
LinkEdge<W>* _next;
LinkEdge(size_t srcIndex,size_t dstIndex,const W& w)
:_srcIndex(srcIndex)
,_dstIndex(dstIndex)
,_w(w)
,_next(NULL)
{
}
};
template<class V,class W,bool digraph = false>
class GraphTable //临接表存储图
{
typedef LinkEdge<W> Edge;
public:
GraphTable(V* vertexs, size_t n)
{
_vertexs.resize(n); //拷贝顶点
_linkTables.resize(n);
for(size_t i = 0 ;i < n ;i++)
{
_vertexs[i] = vertexs[i];
_indexMap[vertexs[i]] = i;
_linkTables[i] = NULL;
}
}
size_t GetVertexIndex(const V& v)//获取顶点的下标
{
return _indexMap[v];
}
void _AddEdge(size_t srcIndex, size_t dstIndex, const W& w) //链表头插
{
Edge* tmp = new Edge(srcIndex,dstIndex,w);
tmp->_next = _linkTables[srcIndex];
_linkTables[srcIndex] = tmp;
}
void AddEdge(const V& src, const V& dst, const W& w)
{
size_t srcIndex = GetVertexIndex(src);
size_t dstIndex = GetVertexIndex(dst);
_AddEdge(srcIndex,dstIndex,w);
if(digraph == false) //无向图
{
_AddEdge(dstIndex,srcIndex,w);
}
}
void GFS(const V& src) //广度优先遍历
{
queue<size_t> q;
size_t srcIndex = GetVertexIndex(src);
vector<bool> visited ;
visited.resize(_vertexs.size(),false);//访问标志
q.push(srcIndex);
while(!q.empty())
{
size_t front = q.front();
q.pop();
if(visited[front] == false)
{
cout<<front<<_vertexs[front]<<"->";
}
else
continue;
Edge* cur = _linkTables[front];
visited[front] = true;
while(cur)
{
if(visited[cur->_dstIndex] == false)
q.push(cur->_dstIndex);
cur = cur->_next;
}
}
cout<<endl;
}
void DFS(const V& src)//深度优先遍历
{
size_t srcIndex = _indexMap[src];
vector<bool> visited;
visited.resize(_vertexs.size(),false);
_DFS(srcIndex,visited);
cout<<endl;
}
void _DFS(size_t srcIndex, vector<bool>& visited)
{
if(visited[srcIndex] == true)
return;
Edge* cur = _linkTables[srcIndex];
cout<<srcIndex<<_vertexs[srcIndex]<<"->";
visited[srcIndex] = true;
while(cur)
{
if(visited[cur->_dstIndex] == false)
{
_DFS(cur->_dstIndex,visited);
}
cur = cur->_next;
}
}
protected:
vector<V> _vertexs;
map<V,size_t> _indexMap;
vector<Edge*> _linkTables; //临接表存储的是边
};
有关图的算法:
最小生成树:
克鲁斯卡尔算法:用于求一个无向图的最小生成树;克鲁斯卡尔算法,每次从图中选取权值最小的边,在选取的时候还应注意N个顶点,则选取N-1条边;且所选的边之间不能构成回路(判断是否构成环可以用并查集来判断,若所选的两条边没有在同一个集合中,则它们不够成环);
普里姆算法(天然判环):
先随意选一个点加入归并点数组之中,然后选出与这个点关联的最小的权的点,输出,并把那个点也加入归并点数组之中,然后再从每一次都从归并点数组之中选一个点道不在这个数组之中的点的最小的权值,再把这个点也加入归并点数组,知道所有的点都在归并点数组之中
迪杰斯特拉算法;