图是一种较为复杂的数据结构,在正常的开发中应用并不广泛。为了了解相应的算法思想,这里只介绍一下常用的实现及操作。
图的概念
1.图的分类
图可以理解为由不同的点以及连接各个点之间的边。根据边的不同性质,可以分为有向图和无向图,以及是否带权重。
上图为无向图,且权重相同,均为1。
2.图的存储
图的存储分为两个结构,边集合和点集合。
这里主要说一下,边集合的建立有两种结构,邻接矩阵和连接表。对于邻接矩阵而言,对于边较少的图,空间利用率不高。对于连接表而言,很难计算点的入度,如果想要计算入度,还需要引入一张逆连接表。
class graph
{
public:
graph(vector<string> points,map<string,string> arcs,vector<int> weights,bool direction);//默认无向图
int insertVex(string sVex);//向图中插入一个点
int insertArc(pair<string,string> sArc,int weight);//向图中插入一条边
int deleteVex(string sVex);//从图中删除一个点
int deleteArc(pair<string,string> sArc);//从图中删除一条边
void print();
void Dfs(visitFunc func);
void Bfs(visitFunc func);
void dijkstra(string s,vector<bool> &visited,vector<int> &parent,vector<int> &dis);//为方便调试,参数都为引用
int getSize();//图中点的个数
private:
int getIdx(string vex);//根据给出的键值找到这个点的索引值
gArc garc;//图的所有边的集合
gVex gvex;//图的所有点的集合
map<string,int> gIdx;//点的键值到索引值的映射
bool direction;//是否有向图
void dfs(int idx,vector<bool> &visited,visitFunc func);
void bfs(int idx,vector<bool> &visited,visitFunc func);
};
graph::graph(vector<string> vexs,map<string,string> arcs,vector<int> weights=vector<int>(),bool direction=0)
{
for(string s:vexs)
{
insertVex(s);
}
int i=0;
for(auto it=arcs.begin();it!=arcs.end();++it,++i)
{
insertArc(*it,weights[i]);
}
this->direction=direction;
}
int graph::getIdx(string vex)
{
auto it=gIdx.find(vex);
if(it==gIdx.end())//未找到
{
return -1;
}
return it->second;
}
int graph::getSize()
{
return gvex.size();
}
int graph::insertVex(string sVex)
{
int idx=gvex.size();
gIdx.insert(make_pair(sVex,idx));
gvex.push_back({sVex,idx});
garc.push_back(vector<Arc>());
return 0;
}
int graph::insertArc(pair<string,string> sArc,int weight=0)
{
int src=getIdx(sArc.first);
int des=getIdx(sArc.second);
int flag=0;//重复标志
cout << "insertArc:weight="<<weight << endl;
if(src==-1||des==-1)
{
cout<<"insertArc:vexs not existed!"<<endl;
return -1;
}
for(auto it=garc[src].begin();it!=garc[src].end();++it)
{
if(it->pos==des)//有重复
{
cout<<"insertArc:arc already exists"<<endl;
it->weight=weight;//修改边的权重值
flag=1;
}
}
if(!flag)
{
garc[src].push_back({des,weight});
}
if(direction)//有向图,到此结束。无向图,还要再插一下
{
return 0;
}
for(auto it=garc[des].begin();it!=garc[des].end();++it)
{
if(it->pos==src)//有重复
{
it->weight=weight;
}
}
if(!flag)
{
garc[des].push_back({src,weight});
}
return 0;
}
int graph::deleteVex(string sVex)
{
int idx=getIdx(sVex);
if(idx==-1)
{
cout<<"deleteVex:not exist:"<<sVex<<endl;
return -1;
}
for(int j=0;j<garc.size();++j)
{
for(int i=0;i<garc[j].size();++i)
{
if(garc[j][i].pos==idx)
{
garc[j].erase(garc[j].begin()+i);
--i;
}
}
}
garc[idx].clear();
gvex[idx].key.clear();
gIdx.erase(sVex);
return 0;
}
int graph::deleteArc(pair<string,string> sArc)
{
int src=getIdx(sArc.first);
int des=getIdx(sArc.second);
for(int i=0;i<garc[src].size();++i)
{
if(garc[src][i].pos==des)
{
garc[src].erase(i+garc[src].begin());
break;
}
}
if(direction)//有向图,删一次就行了,无向图,还要删反方向
{
return 0;
}
for(int i=0;i<garc[des].size();++i)
{
if(garc[des][i].pos==src)
{
garc[des].erase(i+garc[des].begin());
break;
}
}
return 0;
}
图的实现及插入和删除操作
图的遍历
图的遍历,原则是不重不漏,主要有两种方式。
1.深度优先遍历
深度优先遍历是顺着一条边先遍历到底的遍历方式。这里注意在实际开发中要使用STL中栈的结构来模拟递归调用过程,而不要使用系统自己的递归函数栈,因为在实际开发过程中,递归层数较多,这样可能会爆栈。
void graph::dfs(int idx,vector<bool> &visited,visitFunc func)
{
if(visited[idx])
{
return;
}
visited[idx]=1;
if(gvex[idx].idx==idx)
{
func(&gvex[idx]);
}
vector<Arc> arc=garc[idx];
for(auto t:arc)
{
int point=t.pos;
if(!visited[point])
{
dfs(point,visited,func);
}
}
}
void graph::Dfs(visitFunc func)
{
int n=gvex.size();
vector<bool> visited(n,0);
for(int i=0;i<n;++i)
{
if(!visited[i])
{
dfs(i,visited,func);
}
}
}
2.广度优先遍历
广度优先遍历是先将一个点相连的所有点遍历后再继续遍历其他点的遍历方式。同树的广度优先遍历一样,需要用到队列。
void graph::bfs(int idx,vector<bool> &visited,visitFunc func)
{
visited[idx]=1;
if(gvex[idx].idx==idx)
{
func(&gvex[idx]);
}
queue<int> idx_q;
idx_q.push(idx);
while(idx_q.size())
{
int i=idx_q.front();
idx_q.pop();
vector<Arc> arc=garc[i];
for(auto t:arc)
{
int point=t.pos;
if(!visited[point])
{
func(&gvex[point]);
visited[point]=1;
idx_q.push(point);
}
}
}
}
void graph::Bfs(visitFunc func)
{
int n=gvex.size();
vector<bool> visited(n,0);
for(int i=0;i<n;++i)
{
if(!visited[i])
{
bfs(i,visited,func);
}
}
}
dijkstra算法
dijkstra算法是一种寻找最短路径的算法,其主要用到的是贪心思想。主要过程是,从起点出发,依次找寻当时距离最短的点,最后就能形成一条最短路径。
void graph::dijkstra(string s,vector<bool> &visited,vector<int> &parent,vector<int> &dis)
{
int idx=getIdx(s);
if(idx==-1)
{
cout<<"dijkstra:not existed!"<<s<<endl;
return;
}
int n=visited.size();
dis[idx]=0;
parent[idx]=idx;
int i=idx;
while(1)
{
int next=-1,min=INF;
auto arc=garc[i];
for(auto it=arc.begin();it!=arc.end();++it)
{
int point=it->pos;
int d=it->weight;
if(dis[point]>d+dis[i])//有更近的边
{
dis[point]=d+dis[i];
parent[point]=i;
}
}
visited[i]=1;
//扫描一遍,找到最近的点
for(int j=0;j<n;++j)
{
cout << (char)(j+'A')<<",dis:"<<dis[j] <<",visited:"<<visited[j]<< endl;
if(!visited[j]&&min>dis[j])
{
min=dis[j];
next=j;
}
}
if(next==-1)//未找到合适的点
{
break;//说明找完了
}
i=next;
}
}
这里INF意为无穷大,实际值为0x3f3f3f3f。
当然,代码还不完善,比如参数检验,而且代码还有值得优化的地方。这里只是提供一个思想,具体开发中需要进一步完善。