图的应用背景:
1、网络爬虫
2、地图应用:最近距离推荐,最短时间推荐
3、社交网络分析:好友推荐、垃圾用户分析、社交关系分析
4、推荐、精准营销
5、舆情控制、信息传播
6、防欺诈(网络欺诈和电信欺诈)
7、计算生物学:模拟分子运动
图的分类
1、有向图
2、无向图
3、权重图
图的基本概念
顶点集合(vex-set):如上图S(vex) = {‘A’,‘B’,‘D’,‘E’,‘F’}
边集合(arc-set):如上图S(arc) = {<‘A’,‘B’>,<‘A’,‘C’>,<‘A’,‘D’>,<‘B’,‘F’>,<‘B’,‘C’>,<‘C’,‘E’>,<‘D’,‘E’>,<‘E’,‘F’>}
度(degree):无向图中从一个点延伸出去的边数就是该点的度;有向图中包含出度和入度
出度(out-degree):有多少条边指向某点就是该点的出度;
入度(in-degree):从某点出发向外指向延伸的边数就是该点的入度;
图的存储
存储的关键是带点集合和边集合
邻接矩阵
顶点信息存储在一维数组中,边信息存储在二维数组中
优点:很容易算出边邻接关系;以及节点的度(不管出度还是入度)
缺点:边集合存储空间复杂度比较大,图中大量0,空间利用率不高(尤其是在点多边少的情况下);对于无向图,邻接矩阵是对承德,可以只存下半部分。
连接表
顶点集合依然存储在一维数组当中,边集合存储在连接表中。
有向图
无向图
权重无向图
优点:很容易算出邻接关系;以及节点的出度
缺点:很难算出入度,需要遍历整张表;可以建立一张逆连接表(相当于记录入度的表)
图的遍历
从图中某一个顶点出发,访问图中其余顶点,使每个顶点被访问一次且只被访问一次
可以从图中任意一个顶点出发进行遍历
需要解决的问题
1、确定一条搜索路径
2、确保每个顶点被访问到
3、确保每个顶点只能被访问一次
设置辅助数组visited,数组元素的初始值均为false,一旦遍历过就置为true
深度优先搜索
应用:
检测连通分量(独立的图,互相之间没有边或者顶点连接)的个数
两个点是否在一个连通分量中
检测是否构成环;从一个点出发能否回到出发点
广度优先遍历
关键数据结构:队列
应用:游戏中找寻路径问题
迪杰斯特拉算法(dijkstra)
该算法主要解决最短路径问题,采用的是贪心思想
对象:权重图
核心思想:每次从路径最短的点出发遍历相临边,检测修改路径值(确保相邻点也是最短的),从未被确认路径最短的顶点集合中选择最短路径的点,将该点加入确认路径最短的顶点集合,并将该点作为下次遍历相临边的出发点。
代码实现:
#define INF 0x7F7F7F7F
// 16 前8位存储权重信息 | 后8位存储索引
#define WEIGHT(i) ((i&0xff00)>>8)
#define INDEX(i) (i&0xff)
#define MAKE(w,i) ((((w)&0xff)<<8) | i)
typedef vector<list<int>> GraphArc;
typedef string VexType;
typedef struct Vex {
int idx; // 顶点索引
VexType data; // 顶点信息
} GVex;
typedef pair<VexType, VexType> GArc;
typedef bool (*VisitFunc)(GVex &vex);
typedef vector< GVex > GraphVex;
// 应该提供哪些接口? 有向图 无向图 权重图
// 构造图
// 插入边 删除边 顶点之间关
// 插入点 删除点 顶点相关的边信息也要删掉
// 修改权重信息
// 获取节点信息
// 获取最短路径
// 遍历
// 获取边信息
// 有向图 度 信息
class CGraph {
public:
CGraph(bool direct = true) {}
void CreateGraph(vector<VexType> &vex, vector< pair<VexType, VexType> > &arc,
const vector<int> &weights = vector<int>())
{
int i = 0;
for (auto iter = vex.begin(); iter != vex.end(); iter++,i++) {
_graph_vex.push_back({i, *iter});
_vex_idx.insert(make_pair(*iter, i));
}
for (int j = 0; j < i; j++) {
_graph_arc.push_back(list<int>());
}
bool has = weights.size() > 0;
for (size_t i = 0; i < arc.size(); i++) {
InsertArc(arc[i], has ? weights[i] : 0);
}
}
inline size_t VexSize() {return _graph_vex.size();}
// 根据顶点信息查找位置
int LocateVex(VexType &v);
// 根据位置查找顶点信息
VexType GetVex(int idx);
/*
用于遍历某顶点的邻接点,
此时用 vector<list<int>> 存储的邻接信息,
所以不需要实现该接口
VexBegin();
VexNext();
**/
// 修改idx的顶点的值
bool PutVex(int idx, int value);
// 往图中添加顶点
bool InsertVex(VexType &v) {
if (-1 != _get_idx(v))
return false;
int i = (int)_graph_vex.size();
_graph_vex.push_back({i, v});
_vex_idx.insert(make_pair(v, i));
return true;
}
// 往图中删除顶点 除了删除顶点还需要删除相关的边信息
bool DeleteVex(VexType &v) {
int idx = _get_idx(v);
if (-1 == idx)
return false;
auto &vexlist = _graph_arc[idx];
for (auto iter = vexlist.begin(); iter != vexlist.end(); iter++) {
_graph_arc[*iter].remove(idx);
}
_graph_arc[idx].clear();
_graph_vex[idx].data.clear();
_vex_idx.erase(v);
return true;
}
// 往图中添加一条边 <'A', 'B'>
bool InsertArc(const GArc &arc, int weight = 0) {
int iFirst = _get_idx(arc.first);
if (-1 == iFirst)
return false;
int iSecond = _get_idx(arc.second);
if (-1 == iSecond)
return false;
int wFirst = MAKE(weight, iFirst);
int wSecond = MAKE(weight, iSecond);
auto &vexlist = _graph_arc[iFirst];
if (find(vexlist.begin(), vexlist.end(), wSecond) != vexlist.end())
return false;
vexlist.push_back(wSecond);
if (!_direct) { // 无向图 <'B', 'A'>
auto &revlist = _graph_arc[iSecond];
if (find(revlist.begin(), revlist.end(), wFirst) != revlist.end())
return false;
revlist.push_back(wFirst);
}
return true;
}
// 往图中删除一条边
bool DeleteArc(GArc &arc) {
int iFirst = _get_idx(arc.first);
if (-1 == iFirst)
return false;
int iSecond = _get_idx(arc.second);
if (-1 == iSecond)
return false;
auto &vexlist = _graph_arc[iFirst];
bool has = false;
auto iter = vexlist.begin();
for(; iter != vexlist.end(); iter++) {
if (INDEX(*iter) == iSecond) {
has = true;
break;
}
}
if (!has)
return false;
vexlist.erase(iter);
if (!_direct) {
has = false;
auto &revlist = _graph_arc[iSecond];
iter = revlist.begin();
for(; iter != revlist.end(); iter++) {
if (INDEX(*iter) == iFirst) {
has = true;
break;
}
}
if (!has)
return false;
revlist.erase(iter);
}
return true;
}
// A B C D E F
// 0 1 2 3 4 5
// 对图进行深度优先遍历 (不能遍历删除)
void DFSTraverse(vector<bool> &visited, VisitFunc func) {
for (size_t i = 0; i < _graph_vex.size(); i++) { // 有多个
if (!visited[i]) {
_dfs_traverse(i, visited, func);
}
}
}
// 对图进行广度优先遍历 (不能遍历删除)
void BFSTraverse(vector<bool> &visited, VisitFunc func) {
for (size_t i = 0; i < _graph_vex.size(); i++) { // 可能出现多个连通图
if (!visited[i]) {
_bfs_traverse(i, visited, func);
}
}
}
//迪杰斯特拉算法
//param:
//@s: 顶点数据
//@visited:根据索引记录某一顶点是否确认最短路径
//@dis:根据索引记录某一顶点当前最短路径
//@parent:根据索引记录某一顶点的上一顶点,返回后可以用它来判断最短路径的节点是如何组成的
void Dijkstra(VexType s, vector<bool> &visited, vector<int> &dis, vector<int> &parent) {
int iStart = _get_idx(s);
if (-1 == iStart) {
cout << "start not exist" << endl;
return;
}
visited[iStart] = true;
dis[iStart] = 0;
parent[iStart] = iStart;
int i = iStart;
while (true) {
// 更新 相邻节点的距离信息
auto &arclist = _graph_arc[i];
int min = INF, midx = -1;
for (auto iter = arclist.begin(); iter != arclist.end(); iter++) {
int idx = INDEX(*iter);
int wgt = WEIGHT(*iter);
if (dis[idx] > wgt+dis[i]) {
dis[idx] = wgt+dis[i];
parent[idx] = i;
}
}
// 扫描
for (size_t j = 0; j < dis.size(); j++) {
if (!visited[j] && min > dis[j]) {
min = dis[j];
midx = j;
}
}
if (midx == -1)
break;
// 重新选取点
visited[midx] = true;
i = midx;
}
}
private: // 回溯 函数堆栈来实现 实际开发过程 图纵深 尾递归
// 所有的递归 都能够转化为 递推
void _dfs_traverse(int idx, vector<bool> &visited, VisitFunc func) {
if (visited[idx]) return;
visited[idx] = true;
func(_graph_vex[idx]);
auto &arclist = _graph_arc[idx];
for (auto iter = arclist.begin(); iter != arclist.end(); iter++) {
int next = INDEX(*iter);
if (!visited[next]) {
_dfs_traverse(next, visited, func);
}
}
}
void _bfs_traverse(int idx, vector<bool> &visited, VisitFunc func) {
deque<int> queue;
queue.push_back(idx);
visited[idx] = true;
func(_graph_vex[idx]);
while (0 != queue.size()) {
int i = queue.front(); queue.pop_front();
auto &arclist = _graph_arc[idx];
for (auto iter = arclist.begin(); iter != arclist.end(); iter++) {
int next = INDEX(*iter);
if (!visited[next]) {
queue.push_back(next);
visited[next] = true;
func(_graph_vex[next]);
}
}
}
}
int _get_idx(const VexType &v) {
auto iter = _vex_idx.find(v);
if (iter == _vex_idx.end()) return -1;
return iter->second;
}
private:
GraphArc _graph_arc; // 存储边信息
GraphVex _graph_vex; // 存储顶点信息
map<VexType, int> _vex_idx; // 数据对应 idx
bool _direct; // 是否为有向图
};