图
- 1、定义是一种非线性结构,由顶点的集合和顶点间的关系的集合组成的一种数据结构。
Graph = (V,E);
V = {x|x是顶点的集合};
E = {<x,y> |(x,y属于V)}; - 2、图的分类有向图:顶点之间单向指引就是有向图无向图:顶点之间可以互相指引就是无向图
- 3、一些基本概念
1)完全图:在由n个顶点组成的无向图中,若有n(n-1)/2条边,则就是完全无向图
2)权重:在一些图中,边具有与之相关的数值,称为权重。
3)邻接顶点:如果(u,v)是图中的一条边,那么u,v互为邻接点。
4)度:与顶点相关联的边的数目
5)路径:一个顶点到另一个顶点所经过的痕迹
6)连通图:在无向图中,任意两个顶点之间都是连通的。
7)强连通图:在有向图中,若每一对顶点之间都存在路径,就称此图为强连通图。
8)生成树:若一个图有n个顶点,则有n-1条边的就是生成树
一个无向连通图的生成树是它的极小连通子图。
-4、图的存储方法
1)邻接矩阵
A:如何存储:
B:代码如何实现:
template<class V,class W>
class GraphMatrix
{
public:
//构造函数
GraphMatrix(V* v,size_t n,bool isDirected = false,const W& invalue = W())
:_v(v,v+n)
,_isDirected(isDirected)
{
_matrix = new W*[_v.size()];
for(size_t i = 0; i<n; i++)
{
_matrix[i] = new W[_v.size()];
for(size_t j = 0; j<n; j++)
{
_matrix[i][j] = invalue;
}
}
}
//给定一个顶点,如何得到这个顶点的下标
size_t GetIndex(const V& v)
{
for(size_t i = 0; i<_v.size(); i++)
{
if(_v[i] == v)
{
return i;
}
}
assert(false);
return 0;
}
//如何加一条边
void AddEge(const V& v1, const V& v2, const W& w)
{
size_t src = GetIndex(v1);
size_t dest = GetIndex(v2);
_matrix[src][dest] = w;
if(_isDirected = false) //说明是无向图
{
_matrix[dest][src] = w;
}
}
//析构函数
~GraphMatrix()
{
//先释放数据
for(size_t i = 0; i<_v.size(); i++)
{
delete[] _matrix[i];
}
//后释放指针数组
delete[] _matrix;
}
private:
vector<V> _v; //顶点的集合
W** _matrix; //边上权重的集合
bool _isDirected; //是否是与有向图
};
2)邻接表
A:如何存储:
节点的设置:
template<class W>
struct GraphLinkNode
{
W _weight; //权重
GraphLinkNode<W*> _next; //下一个节点
size_t _src;
size_t _dest;
GraphLinkNode(size_t src,size_t dest,const W& weight = W())
:_weight(weight)
,_next(NULL)
,_src(src)
,_dest(dest)
{}
};
B:代码如何实现
template<class V, class W>
class GraphLink
{
typedef LinKEdge<W> Edge;
public:
GraphLink(V* vertexs, size_t n, const W& invalid = W(), bool isDirected = false)
:_isDirected(isDirected)
{
_vertexs.resize(n);
_vertexs.assign(vertexs, vertexs+n);
_linkTables.resize(n, NULL);
}
size_t GetVertexIndex(const V& v)
{
for (size_t i = 0; i < _vertexs.size(); ++i)
{
if (_vertexs[i] == v)
{
return i;
}
}
assert(false);
return 0;
}
void _AddEdge(size_t src, size_t dst, const W& w)
{
// 头插
LinKEdge<W>* edge = new Edge(src, dst, w);
edge->_next = _linkTables[src];
_linkTables[src] = edge;
}
void AddEdge(const V& v1, const V& v2, const W& w)
{
size_t src = GetVertexIndex(v1);
size_t dst = GetVertexIndex(v2);
_AddEdge(src, dst, w);
if (_isDirected == false)
{
_AddEdge(dst, src, w);
}
}
protected:
vector<V> _vertexs; // 顶点集合
vector<Edge*> _linkTables; // 邻接表
bool _isDirected;
};
邻接矩阵和邻接表的比较及各自的用途:
说明:
A:就定义来说:
邻接矩阵更适合直接定位
邻接表适合搜寻相邻节点
B:存储
邻接矩阵:在无向图的存储中,邻接矩阵的存储方式是一种对称矩阵,这样的存储方式可以节省很大的内存空间
邻接表:在无向图的存储中,是要将重复的元素再次进行存储的,因此会有空间上的开销
C:找寻元素的相邻边
邻接矩阵:要按行,列,依次进行查找,时间复杂度很高
邻接表:直接就遍历依次指向此节点的链表即可
- 5、图的遍历
1)分类:
深度优先遍历
广度优先遍历
2)代码如何实现
A:深度优先遍历
void DFS(const V& src)
{
size_t index = GetVertexIndex(src);
vector<bool> visited(_vertexs.size(),false);
visited[index] = true;
cout<<_vertexs[index]<<"->";
_DFS(index,visited);
}
void _DFS(size_t src,vector<bool>& visited)
{
Edge* cur = _linkTables[src];
while(cur)
{
size_t dst = cur->_dst;
if(visited[dst] == false)
{
cout<<_vertexs[dst]<<"->"<<" ";
visited[dst] = true;
_DFS(dst,visited);
}
cur = cur->_next;
}
}
B:广度优先遍历
void BFS(const V& src)
{
size_t index = GetVertexIndex(src);
vector<bool> visited(_vertexs.size(),false);
queue<int> q;
q.push(index);
while(!q.empty())
{
size_t front = q.front();
if(visited[front] == false)
{
cout<<_vertexs[front]<<"->";
visited[front] = true;
Edge* cur = _linkTables[front];
while(cur)
{
size_t dst = cur->_dst;
if(visited[dst] == false)
{
q.push(dst);
visited[dst] = true;
cout<<_vertexs[dst]<<"->";
}
cur = cur->_next;
}
}
q.pop();
}
}