图的基本内容
邻接表和邻接矩阵的实现方式将在具体算法实现中完成,不做赘述。
邻接矩阵的遍历:
1.深度优先遍历:(递归实现)一直走不回头
(1)访问指定起始地点。
(2)若当前访问顶点的邻接顶点有未被访问的顶点,就任选一个访问。如果没有就回退到最近访问的顶点,直到与起始顶点相通的所有点被遍历完。
(3)若途中还有顶点未被访问,则再选一个点作为起始顶点。重复步骤2(针对非连通图)。
2.广度优先遍历:(队列实现)走完一个点的所有临界点再往下走
(1)访问指定起始点。
(2)访问当前顶点的邻接顶点有未被访问的顶点,并将之放入队列中。
(3)删除队列的队首节点。访问当前队列的队首,重复步骤2。直到队列为空。
(4)若若途中还有顶点未被访问,则再选一个点作为起始顶点。重复步骤2。(针对非连通图)。
#include<iostream>
#include<queue>
using namespace std;
const int MaxSize = 100;
int MinEdge(int a[],int n)
{
int min = 100;
int index = 1;
for(int i = 1;i<=n;i++)
{
if(a[i]<=min&&a[i]!=0)
{
min = a[i];
index = i;
}
}
return index;
}
template <typename DataType>
class MGraph
{
public:
MGraph(DataType a[],int n,int e);
~MGraph(){};
void DFT(int v);
void BFT(int v);
private:
DataType vertex[MaxSize];
int visted[MaxSize];
int edge[MaxSize][MaxSize];
int vertexNum,edgeNum;
};
template <typename DataType>
MGraph<DataType>::MGraph(DataType a[],int n,int e)
{
vertexNum = n;
edgeNum = e;
int w;
for(int i = 1;i<=n;i++)
{
vertex[i] = a[i];
}
for(int i = 1;i<=n;i++)
{
visted[i] = 0;
}
for(int i = 1;i<=n;i++)
{
for(int j = 1;j<=n;j++)
{
cin>>edge[i][j];
}
}
}
template <typename DataType>
void MGraph<DataType>::DFT(int v)//深度优先搜索
{
cout<<vertex[v];//输出顶点具体信息
visted[v] = 1;//修改标记
for(int i = 1;i<=vertexNum;i++)//遍历图中的所有顶点
{
if(visted[i]!=1&&edge[v][i]<100)//如果与已知顶点相邻并且没有被访问过
DFT(i);//深度优先遍历这个邻接点
}
}
template <typename DataType>
void MGraph<DataType>::BFT(int v)//广度优先搜索
{
queue<int>q;
q.push(v);//起始顶点无条件入队
cout<<vertex[v];//输出顶点信息
visted[v] = 1;//修改起始点的访问标识符
while(!q.empty())//当队列不空时
{
v = q.front();//取队首元素
q.pop();//删除队首元素
for(int i = 1;i<=vertexNum;i++)//遍历所有的顶点
{
if(visted[i]!=1&&edge[v][i]<100)//如果与已知顶点相邻并且没有被访问过
{
cout<<vertex[i];
visted[i] = 1;//修改访问标志
q.push(i);//邻接点入队
}
}
}
}
邻接表的遍历:
最小生成树:(加点)
1.Prim算法
将图分成两部分,一部分为待求最小生成树,一部分为没有选过的点。
(1)起始最小生成树为空。
(2)将一个点加入到最小生成树的集合中,遍历所有与之相邻的点,选择权值最小的邻接点加入最小生成树集合中。
(3)遍历与新加入顶点相邻的点,选择权值最小的邻接点加入最小生成树中,重复步骤2,直到所有点都加入最小生成树中。
Prim算法的核心是记录了目前未选取顶点与最小生成树的最小权值,选完后更新记录,直到算法完成。
#include<iostream>
#include<queue>
using namespace std;
const int MaxSize = 100;
int MinEdge(int a[],int n)//用于选取最小权值的边
{
int min = 100;
int index = 1;
for(int i = 1;i<=n;i++)
{
if(a[i]<=min&&a[i]!=0)
{
min = a[i];
index = i;
}
}
return index;
}
template <typename DataType>
class MGraph
{
public:
MGraph(DataType a[],int n,int e);
~MGraph(){};
void Prim(int v);
private:
DataType vertex[MaxSize];
int visted[MaxSize];
int edge[MaxSize][MaxSize];
int vertexNum,edgeNum;
};
template <typename DataType>
MGraph<DataType>::MGraph(DataType a[],int n,int e)
{
vertexNum = n;
edgeNum = e;
int w;
for(int i = 1;i<=n;i++)
{
vertex[i] = a[i];
}
for(int i = 1;i<=n;i++)
{
visted[i] = 0;
}
for(int i = 1;i<=n;i++)
{
for(int j = 1;j<=n;j++)
{
cin>>edge[i][j];
}
}
}
template <typename DataType>
void MGraph<DataType>::Prim(int v)
{
int adjvex[MaxSize];//记录与最小生成树相邻的顶点
//adjvex[i] = j;表达的意思是整个图中顶点i与生成树中的顶点j相邻
int lowcost[MaxSize];//记录与最小生成树相邻的顶点的最小权值
//lowcost[i] = w;表达的意思是整个图中顶点i与生成树的最小权值是w
for(int i = 1;i<=vertexNum;i++)
{
lowcost[i] = edge[v][i];
adjvex[i] = v;//初始图中所有顶点与指定顶点相邻
}
lowcost[v] = 0;//将指定顶点加入到最小生成树中
int j;
for(int k = 1;k<vertexNum;k++)
{
j = MinEdge(lowcost,vertexNum);//选出最小值的点
cout<<lowcost[j]<<" ";
lowcost[j] = 0;
for(int i = 1;i<=vertexNum;i++)//遍历所有的顶点更新记录
{
if(edge[i][j]<lowcost[i])
{
lowcost[i] = edge[i][j];
adjvex[i] = j;
}
}
}
}
2.Kruskal算法(加边)
(1)对图中的所有边进行排序;
(2)初始化最小生成树为包括所有点,0条边的树
(3)如果最小权值边与生成树不在同一连通分量,加入最小生成树中
(4)如果在同一连通分量中,略过这条边
(5)重复步骤4和5,直到选出顶点数-1条边
使用边集数组存储边,应用并查集合并。
合并两棵树:构建父子关系
判断两棵树是否为同一连通分量的方案:检查它们的根是否相同
#include <iostream>
#include <algorithm>
using namespace std;
struct Edge{//定义边集
int from,to,weight;
};
bool cmp(Edge&e1,Edge&e2){
return e1.weight<e2.weight;
}
class EdgeSet
{
private:
Edge*edge;
int vertexNum;
int edgeNum;
int FindRoot(int*parent,int index);//根查找函数
public:
EdgeSet(int n,int e);
~EdgeSet(){
};
void Kruskal();
};
EdgeSet::EdgeSet(int n,int e){
vertexNum = n;
edgeNum = e;
edge = new Edge[edgeNum];
int index = 0;
int a[vertexNum+1][vertexNum+1];
for(int i = 1;i<=vertexNum;i++)
for(int j = 1;j<=vertexNum;j++){
cin>>a[i][j];
}
for(int i = 1;i<=vertexNum;i++)
for(int j = i;j<=vertexNum;j++){
if(a[i][j]<100&&a[i][j]>0){
edge[index].from = i;
edge[index].to = j;
edge[index].weight = a[i][j];
index++;
}
}
sort(edge,edge+index,cmp);
}
int EdgeSet::FindRoot(int*parent,int index){
int root = index;
while(parent[root]>-1){
root = parent[root];
}
return root;
}
void EdgeSet::Kruskal(){
int*parent = new int[vertexNum+1];
for(int i = 0;i<=vertexNum;i++)
parent[i] = -1;//所有的顶点均没有双亲
int count = 0;//记录边数
for(int k = 0;k<edgeNum;k++){
int i = FindRoot(parent,edge[k].from);
int j = FindRoot(parent,edge[k].to);
if(i!=j){//判断是否为同一连通分量
cout<<edge[k].from<<" "<<edge[k].to<<" ";
parent[j] = i;//合并
count++;
if(count==vertexNum-1)
break;
}
}
}
最短路径算法(单源点到所有点)
1.Dijkstra算法:
在与指定单源点邻接的所有点中,必有一条最短路径长度是两点之间的权值。因此最短路径可以分成两种情况:一种是已经存在的路径,另一种是经过其他的顶点构建的新的路径
(1)初始路径为单源点相邻的顶点的权值,如果没有邻接则用一个默认的最大值代替
(2)选择最小权值的点
(3)更新最短路径长度
void MGraph::Dijkstra(int v){
int*dist = new int[vertexNum];//记录单源点到各顶点的路径长度,dist[i]:单源点到第i点的路径长度
string*path = new string[edgeNum];//用于打印路径具体情况
for(int i = 0;i<vertexNum;i++){//初始化
dist[i] = edge[v][i];
if(dist[i]!=MaxSize)
path[i] = vertex[v]+" "+vertex[i];
else
path[i] = "";
}
int index;
for(int num = 1;num<vertexNum;num++){
index = Min(dist,vertexNum);//选择
for(int i = 0;i<vertexNum;i++){//尝试所有的点
if(dist[i]>dist[index]+edge[index][i]){//更新
dist[i] = dist[index]+edge[index][i];
path[i] = path[index]+" "+vertex[i];
}
}
dist[index] = 0;//加入最短路径
}
}
2.Floyd算法:
相对于Dijkstra更朴素的算法,走所有的路,选最短的那条。
n与n个点之间的所有路径,可以调用n次Dijkstra算法等价实现
void MGraph::Floyd(){
int dist[MaxSize][MaxSize];//记录路径长度
string path[MaxSize][MaxSize];
for(int i = 0;i<vertexNum;i++){//初始化路径长度为权值大小
for(int j = 0;j<vertexNum;j++){
dist[i][j] = edge[i][j];
if(edge[i][j]!=MaxSize&&i!=j){
path[i][j] = vertex[i]+" "+vertex[j];
}
else
path[i][j] = "";
}
}
for(int k = 0;k<vertexNum;k++){//在第i个点和第j个点之间尝试所有的点
for(int i = 0;i<vertexNum;i++){
for(int j = 0;j<vertexNum;j++){
if(dist[i][j]>dist[i][k]+dist[k][j]){//更新
dist[i][j] = dist[i][k]+dist[k][j];
path[i][j] = path[i][k]+" "+path[k][j].substr(3);
}
}
}
}
}
拓扑排序:
先说一下拓扑排序的概念:
对一个有向无环图G进行拓扑排序,是将G中所有顶点排成一个线性序列,使得图中任意一对顶点u和v,若边(u,v)∈E(G),则u在线性序列中出现在v之前。通常,这样的线性序列称为满足拓扑次序的序列,简称拓扑序列。简单的说,由某个集合上的一个偏序得到该集合上的一个全序,这个操作称之为拓扑排序。
———出自百度
算法描述:
(1)选择没有箭头指入的节点中的任一个,可以作为合法序列的起点,放入序列。
(2)当我们将某个节点放入序列后,去掉该节点以及从该节点指出的所有箭头。在新的图中,选择没有箭头指向的节点,作为序列中的下一个点。
(3) 重复上面的过程,直到所有的节点都被放入序列。
可以用队列实现以上算法
(1)如果顶点的入度为0,加入队列中
(2)当队列不为空时
a.访问队首元素
b.将所有与队首元素相连的所有点入度减一
c.检查邻接点的入度是否为0,为0入队
#include <iostream>
#include <string.h>
#include <stack>
#include <sstream>
using namespace std;
string itos(int i){
stringstream s;
s<<i;
return s.str();
}
struct EdgeNode{
int index;
EdgeNode*next;
};
struct Vertex{
string vertex;
int in;
EdgeNode*first;
};
class AdjList{
private:
Vertex*v;
int vertexNum;
int edgeNum;
public:
AdjList(int n,int e);
~AdjList();
void TopSort();
};
AdjList::AdjList(int n,int e){
vertexNum = n;
edgeNum = e;
v = new Vertex[vertexNum+1];
for(int i = 1;i<=vertexNum;i++){
v[i].in = 0;
v[i].first = NULL;
v[i].vertex = "v"+itos(i);
}
int from,to;
for(int i = 1;i<=edgeNum;i++){
cin>>from>>to;
EdgeNode*s = new EdgeNode;
s->index = to;
s->next = v[from].first;
v[from].first = s;
v[to].in++;
}
}
AdjList::~AdjList(){
EdgeNode*p = NULL;
EdgeNode*q = NULL;
for(int i = 1;i<=vertexNum;i++){
p = q = v[i].first;
while(p!=NULL){
p = p->next;
delete q;
q = p;
}
}
}
void AdjList::TopSort(){
stack<Vertex>s;
for(int i = vertexNum;i>0;i--)
if(v[i].in==0)
s.push(v[i]);
while(!s.empty()){
cout<<s.top().vertex<<" ";
EdgeNode*p = s.top().first;
s.pop();
while(p!=NULL){
v[p->index].in--;
if(v[p->index].in==0)
s.push(v[p->index]);
p = p->next;
}
}
}
关键路径:
struct Edge{
int from,to,e,l;
};
class AOE{
private:
int edgeNum;
int vertexNum;
Edge*edgeset;//边集
int edge[MaxSize][MaxSize];//邻接矩阵
string*vertex;//点集
public:
AOE(int n,int e);
~AOE(){
};
void CriPath();
};
AOE::AOE(int n,int e){
vertexNum = n;
edgeNum = e;
edgeset = new Edge[edgeNum+1];
vertex = new string[vertexNum+1];
int w,from,to;
for(int i = 1;i<=vertexNum;i++){
vertex[i] = 'v'+itos(i);
for(int j = 1;j<=vertexNum;j++){
edge[i][j] = MaxSize;
}
}
for(int i = 1;i<=edgeNum;i++){
cin>>from>>to>>w;
edge[from][to] = w;
edgeset[i].from = from;
edgeset[i].to = to;
edgeset[i].e = 0;
edgeset[i].l = 0;
}
}
void AOE::CriPath(){
int visit[vertexNum+1] = {
0
};
int vl[vertexNum+1] = {
MaxSize
};//事件最早发生时间
int ve[vertexNum+1] = {
0
};//事件最晚发生时间
ve[1] = 0;
queue<int>q;
q.push(1);
int i;
visit[1] = 1;
while(!q.empty()){
i = q.front();
q.pop();
for(int j = 1;j<=vertexNum;j++){
if(edge[i][j]!=MaxSize&&ve[j]<edge[i][j]+ve[i])
ve[j] = edge[i][j]+ve[i];
if(!visit[j])
q.push(j);
visit[j] = 1;
}
}
q.push(vertexNum);
for(int i = 1;i<=vertexNum;i++){
visit[i] = 0;
vl[i] = ve[vertexNum];
}
while(!q.empty()){
i = q.front();
//cout<<i<<" ";
q.pop();
for(int j = vertexNum;j>0;j--){
//for(int j = 1;j<=vertexNum;j++){
if(edge[j][i]!=MaxSize&&vl[j]>vl[i]-edge[j][i]){
vl[j] = vl[i]-edge[j][i];
}
if(!visit[j])
q.push(j);
visit[j] = 1;
}
}
for(int i = 1;i<=edgeNum;i++){
edgeset[i].e = ve[edgeset[i].from];
edgeset[i].l = vl[edgeset[i].to]-edge[edgeset[i].from][edgeset[i].to];
if(edgeset[i].e==edgeset[i].l)
cout<<vertex[edgeset[i].from]<<" ";//<<vertex[edgeset[i].to]<<" ";
}
cout<<vertex[vertexNum];
}