floyd-warshall算法(邻接矩阵) 能够解决多源最短路径
dijkstra算法(邻接矩阵) 能够解决没有负权边的单源最短路径
dijkstra算法的优先队列优化(邻接矩阵)。
dijkstra算法的优先队列优化(邻接表)
dijkstra算法的堆优化(邻接矩阵)
bellman-ford算法(邻接矩阵) 能够解决负权边的单源最短路径
bellman-ford算法的队列优化(邻接矩阵)
bellman-ford算法的队列优化(邻接表)
实际上是动态规划,通过顶点来松弛顶点到顶点之间的距离。
直接看啊哈算法上的讲解 很清楚
#include
#define INT 99999999//这个地方不能直接定义为INT_MAX,因为INT_MAX加上一个数后就变成一个负数了。
using namespace std;
int linjie[9999][9999];//邻接矩阵
int n,m;//n表示顶点个数,m表示边的个数
int x,y,l;//从x点到y点的距离为l
class heat
{
public:
void chushihua()//初始化邻接表,除了i等于j的点为0,其余的都为很大的一个数
{
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
{
if(i==j)
linjie[i][j]=0;
else
linjie[i][j]=INT;
}
}
public:
void fuzhi()//将题目给定的信息输入到已经初始化好的邻接表中去
{
for(int i=0;i>x>>y>>l;
linjie[x][y]=l;
}
}
public:
void hexin()//算法的核心部分
{ //通过k顶点来松弛i顶点到j顶点的距离
for(int k=1;k<=n;k++)//k等于1时是代表从i顶点到j顶点只经过1顶点的最短路,k等于2时是代表从i顶点到j顶点可以经过1顶点和2顶点的最短路,往下依次类推
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
if(linjie[i][j]>(linjie[i][k]+linjie[k][j]))//如果(i顶点直达j顶点的距离)大于(i顶点到k顶点的距离加上k顶点到j顶点距离),就更新邻接矩阵
linjie[i][j]=linjie[i][k]+linjie[k][j];
}
public:
void shuchu()//输出邻接矩阵
{
for(int i=1;i<=n;i++)
{
for(int j=1;j<=n;j++)
cout<>n>>m;
p.chushihua();
p.fuzhi();
p.hexin();
p.shuchu();
}
建议看《啊哈算法》
实际是一种贪心算法,每次选离起始点最近的未被访问过的顶点,当找到这个点后,这个点的最短路径就求出来了,在后续的松弛操作中这个点的最短路径不会发生变化了,因为在所有点中除了这个点和已经求出最短路径的点 ,其余的点必须通过这个点,或者其他未被访问过的点来求,所以通过其他路径求出的最短路径肯定比当前所确定的最短路径长。
该算法求出点的顺序是:先求出离起始点最短距离最近的点,然后依次求出第2近的,第3近的.......
#include
#define INT 99999999
using namespace std;
int visited[9999];//用来标记从1顶点到i顶点是否已经是最短路径
int linjie[9999][9999];//邻接矩阵
int dis[9999];//用来储存当前从1顶点到i顶点的最短路径
int n,m;//n是顶点的个数 m是边的条数
int x,y,l;//从x顶点到y顶点的距离为l
class Linjie//这个类是对邻接表的处理
{
public :
void chushihua()//初始化邻接矩阵
{
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
linjie[i][j]=i==j?0:INT;
}
public:
void fuzhi(int x,int y,int l)//将题目给定的边的信息输入到邻接表中去
{
linjie[x][y]=l;
}
};
class heat//这是dijkstra算法的类
{
public:
void dischushihua()//初始化dis数组,相当于先对1顶点开始扩展
{
for(int i=1;i<=n;i++)
dis[i]=linjie[1][i];
visited[1]=1;//对1顶点扩展完了 进行标记
}
public:
void hexin()//算法分核心部分
{
for(int i=1;i>n>>m;
Linjie a;
a.chushihua();
for(int i=0;i>x>>y>>l;
a.fuzhi(x,y,l);
}
heat q;
q.dischushihua();
q.hexin();
q.dayin();
}
注意,要是用邻接矩阵储存图的话,其算法的时间复杂度还是o(n^2)(因为对邻接矩阵初始化时,时间复杂度为o(n^2)),如果要使算法的时间复杂度降到o(n*log n),必须使用邻接表来储存图
假设操作一:找当前离起始点的最短的未被访问过的点
操作二:对离起始点的最短的未被访问过的点的出边点访问
通过dijkstra算法的代码可以看出,该算法的时间复杂度是o(n^2)。操作一的时间复杂度为o(n),但是可以用优先队列使操作一的时间复杂度降到o(log n),那么dijkstra算法的时间复杂度就可以变为o(n*log n),那就是用优先队列来储存剩余未被确定最短路的顶点,然后每次弹出队列顶部的顶点来进行操作二,这里利用优先队列的性质。
#include
#define INT 999999999
using namespace std;
int n,m;//n为顶点个数,m为边的条数
int dis[9999];//dis[i]表示当前i顶点到起点的最短路径
int e[9999][9999];//邻接矩阵
int u,v,w;//起点,终点,权值
struct node
{
int z,s;//z表示顶点编号,s表示z顶点到起始点的当前的最短距离
friend bool operator < (node n1,node n2)//定义优先队列以结构中某一个值为标准的排序规则的函数
{
return n1.s>n2.s;//以s为标准的从小到大的排序规则
}
node(int z,int s):z(z),s(s){};
};
class linjie
{
public:
void chushihua()//初始化邻接矩阵
{
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
e[i][j]=(i==j)?0:INT;
}
public:
void fuzhi(int u,int v,int w)//给邻接矩阵赋值
{
e[u][v]=w;
}
};
class meat
{
public:
void hexin()//核心函数
{
priority_queue< node >q;//定义一个优先队列
q.push(node(1,0));//往队列中加入起点的信息
dis[1]=0;
while (!q.empty())
{
node x=q.top();//弹出队列顶部的元素
q.pop();//消除这个元素
for(int i=1;i<=n;i++)
{
if(e[x.z][i]dis[x.z]+e[x.z][i])
{
dis[i]=dis[x.z]+e[x.z][i];//更新dis数组
q.push(node(i,dis[i]));//往队列中加入i顶点的信息 注意:这个地方如果在队列中已经有了i顶点,也要在加一遍。当然,直接把原先队列中的i顶点直接替换掉最好,但是无法找到i顶点在队列中的位置,所以无法替换。只能在往队列中加入一个新的i顶点的信息,一旦这个新的i顶点的信息加入后,说明原先在队列中的那个i顶点的信息已经没有意义了。
}
}
}
}
public:
void dayin()//打印dis数组
{
for(int i=1;i<=n;i++)
cout<>n>>m;
linjie p;
p.chushihua();
for(int i=0;i>u>>v>>w;
p.fuzhi(u,v,w);
}
for(int i=1;i<=n;i++)
dis[i]=INT;
meat f;
f.hexin();
f.dayin();
}
用邻接表可以真正实现dijkstra算法的时间复杂度为o(n*logn)。
#include
#define INT 9999999999
using namespace std;
int n,m;
int u[9999],v[9999],w[9999];//u[i]点到v[i]点的距离为w[i];
int dis[9999];
int frist[9999],next[9999];//frist[i]保存i顶点的第一条边的编号 next[i]储存"编号为i的边"的"下一条边"的编号。
struct node
{
int z;//顶点编号
int s;//该顶点到起始点的当前最短距离
friend bool operator < (node n1,node n2)//定义优先队列以结构中某一个值为标准的排序规则的函数
{
return n1.s>n2.s;//以s为标准的从小到大的排序规则
}
node(int z,int s):z(z),s(s){};
};
class linjie
{
public:
void chushihua()//初始化frist数组
{
memset(frist,-1,sizeof(frist));
}
public:
void fuzhi(int u,int v,int w,int i)//这里必须把边的编号也得传进来,因为frist数组和next数组的赋值都得需要这个编号
{
next[i]=frist[u];//next[i]每次都等于最新的u点到v点的边的编号的前一个u点到v点的边的编号
frist[u]=i;//frist[u]每次都等于最新的u点到v点的边的编号
}
};
class dijkstra
{
public:
void hexin()
{
priority_queue< node >q;//定义优先队列
q.push(node(1,0));//将1号顶点,即起始点放入队列中去
dis[1]=0;
while (!q.empty())
{
node x=q.top();//优先队列队顶的元素表示当前离起始点最短路径最短的那个顶点(假设为g顶点),x.z表示g点的编号
q.pop();
int k=frist[x.z];//k表示g点的第一条出边的编号,其实g顶点就是u[k]顶点,g顶点的第一个出边点就是v[k]。
while (k!=-1)//当k等于-1时,说明g点的所有出边已经都访问完成了
{
if(dis[v[k]]>dis[x.z]+w[k])//这个条件满足,说明可以通过g点使得起始点到v[k]点的当前最短路径变短
{
dis[v[k]]=dis[x.z]+w[k];//更新dis数组
q.push(node(v[k],dis[v[k]]));//让该点入队列
}
k=next[k];//更新k点,继续访问g点的其他出边
}
}
}
public:
void dayin()//输出dis数组
{
for(int i=1;i<=n;i++)
cout<>n>>m;
linjie p;
p.chushihua();
for(int i=0;i>u[i]>>v[i]>>w[i];
p.fuzhi(u[i],v[i],w[i],i);
}
for(int i=1;i<=n;i++)
dis[i]=INT;
dijkstra l;
l.hexin();
l.dayin();
}
注意,要是用邻接矩阵储存图的话,其算法的时间复杂度还是o(n^2)(因为对邻接矩阵初始化时,时间复杂度为o(n^2)),如果要使算法的时间复杂度降到o(n*log n),必须使用邻接表来储存图
除了优先队列有这个性质外,小根堆也可以实现和优先队列一样的效果,其实堆结构实际上就是优先队列,如果自己写堆的操作的话,操作一的时间复杂度为o(log n),那么dijkstra算法总的时间复杂度为o(n*log n);
#include
#define INT 99999999
using namespace std;
int linjie[9999][9999];//邻接矩阵
int dis[9999];//用来储存当前从1顶点到i顶点的最短路径
int visited1[9999];//用来标记放入小根堆中的顶点,如果小根堆中存在i顶点,那么visited1[i]就等于1
int visited2[9999];//visited2[i]表示i顶点在小根堆数组中的哪个位置
int n,m;//n是顶点的个数 m是边的条数
int x,y,l;//从x顶点到y顶点的距离为l
int size=1;//size表示当前小根堆数组的元素个数
struct node
{
int z,s;//z表示顶点编号 s表示从起点到z顶点的当前最短距离
} a[9999];//小根堆数组;
void swap(int i,int j)
{
visited2[a[i].z]=j;//如果小根堆数组中i,j位置上的node结构交换,那么必须更新visited2数组中a[i].z,a[j].z位置上的值(交换一下visited2数组a[i].z,a[j].z位置上的值)。
visited2[a[j].z]=i;
node temp=a[i];
a[i]=a[j];
a[j]=temp;
}
void heapinsert(int i)//将数组中第i个数放入小根堆中
{
int index=i;//用index表示当前位置,每次都会更新index
while (a[index].sa[index+1].s&&((index+1)(dis[u]+linjie[u][j]))//第一个条件:u点到j点有直达的路径。第二个条件:通过u点中转可以使起点到j点的距离变短
{
dis[j]=dis[u]+linjie[u][j];//更新
if(!visited1[j])//如果满足这个条件,说明在小根堆数组中没有j这个顶点
{
a[size].s=dis[j];//将j顶点的信息放入到小根堆数组中去
a[size].z=j;
visited2[j]=size;//j顶点在小根堆数组中的位置为size位置
heapinsert(size);
visited1[j]=1;//标记
size++;//小根堆数组长度+1
}
else//如果上面条件没有满足,说明大根堆数组中已经存在j顶点了,只需要更新一下j顶点的信息就行了
{
a[visited2[j]].s=dis[j];//更新到起点的距离
heapinsert(visited2[j]);//更新完成后,肯定visited2[j]位置上的数字变小了,有可能不是小根堆了,把它在调成小根堆
}
}
}
}
}
public:
void dayin()//打印邻接矩阵
{
for(int i=1;i<=n;i++)
cout<>n>>m;
Linjie a;
a.chushihua();
for(int i=0;i>x>>y>>l;
a.fuzhi(x,y,l);
}
for(int i=1;i<=n;i++)//初始化dis数组。
dis[i]=INT;
heat q;
q.hexin();
q.dayin();
}
还是建议看《啊哈算法》,上面讲的很清楚
其实还是动态规划,通过边来松弛顶点到顶点的距离
#include
#define INT 99999999;
using namespace std;
int n,m;//顶点格式n 边条数m
int u[9999];//存放开始的节点 u
int v[9999];//存放结束的节点 v
int w[9999];//存放u到v的路径长度
int dis[9999];//表示i节点到起始节点的当前最短路径
class heat
{
public:
void hexin()//该算法的核心部分
{
for(int i=1;idis[u[j]]+w[j])
return false ;//如果存在负权回路,返回false
return true;//如果不存在,返回true
}
public:
void shuchu()//输出dis数组
{
for(int i=1;i<=n;i++)
cout<>n>>m;
for(int i=1;i<=m;i++)
cin>>u[i]>>v[i]>>w[i];
for(int i=1;i<=n;i++)
dis[i]=INT;//开始先将dis数组都定义为无限大
dis[1]=0;//我们假设该题目是从1节点出发的,那么1节点到1节点的最短路长度为0
heat p;
p.hexin();
p.shuchu();
}
在实施每一次松弛操作后,就会有一些顶点已经求的最短路径,此后这些顶点的最短路路径的估计值(dia数组中的值)就会一直保持不变,不在受后续松弛操作的影响,但每次还要判断是否需要松弛,浪费了时间。所以运用一个队列来保存上一次通过松弛操作最短路径发生了变化的点,每次对队列的对首顶点进行松弛操作就可以,这样就会避免不必要的松弛操作。
#include
#define INT 99999999
using namespace std;
int n,m;//n表示顶点个数,m表示边的条数
int e[9999][9999];//邻接矩阵
int u,v,w;//u点到v点的距离为w
int dis[9999];//dis[i]表示i点当前到起始点的最短距离
bool visited1[9999];//标记队列中是否存在i顶点,为啥这次可以用数组来标记队列中是否存在i顶点,因为这次队列中只保存顶点的编号,不保存顶点到起始点的当前最短距离。如果队列中存在i顶点,只要不再往队列中加入i顶点就行了,不需要更新队列中的值,所以不需要知道i顶点在队列中的位置。
queue < int > q;
class linjie
{
public:
void chushihua()//初始化邻接矩阵
{
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
e[i][j]=i==j?0:INT;
}
public:
void fuzhi(int u,int v,int w)//往邻接矩阵中赋值
{
e[u][v]=w;
}
};
class meat
{
public:
void hexin()
{
q.push(1);//让起始点,即1点入队
visited1[1]=true;
dis[1]=0;
while (!q.empty())//直到队列为空时才结束,只要队列中有数,说明在上一轮更新中还有点(假设为i点)的最短路径变短,那么这次更新与i点的出边的点的最短路径可能也会变小
{
int u=q.front();
q.pop();
visited1[u]=false;//这个地方为啥还要把标记撤掉,因为u这个顶点在后面程序运行中有可能通过其他的顶点能够再次松弛成功,这时u顶点到起始点的距离又变短了,就又可以通过u顶点来更新其他顶点到起始点的距离了,如果这个地方不把u顶点的标记消除,那么u顶点就无法再次进入队列中去,那么就无法通过u顶点更新其他的顶点
for(int i=1;i<=n;i++)
{
if(e[u][i]dis[u]+e[u][i])//条件一:i点是u点出边上的点。条件二:i点到起始点的当前距离可以通过u点来松弛
{
dis[i]=dis[u]+e[u][i];//更新dis数组
if(!visited1[i])//队列中不存在i点
{
q.push(i);//如果程序到这,说明在这一轮松弛操作中i点松弛成功了(i点到起始点的当前最短路径变短了),就把i点入队列,以后再通过i点来松弛其他的点
visited1[i]=true;
}
//当队列中存在了i点,就不需要在往队列中加入这个i点了,因为只要程序能到达这里,说明i点到起始点的当前最短路径肯定比之前找到的i点到起始点的最短路径短,所以通过i点松弛其他点的时候用的是i点到起始点当前的最短路径(即当前知道的最短的路径,从而保证了算法的正确性)
}
}
}
}
public:
void shuchu()//输出dis数组
{
for(int i=1;i<=n;i++)
cout<>n>>m;
linjie p;
p.chushihua();
for(int i=0;i>u>>v>>w;
p.fuzhi(u,v,w);
}
for(int i=1;i<=n;i++)
dis[i]=INT;
meat t;
t.hexin();
t.shuchu();
}
只不过换一种图的储存方式。
#include
#define INT 99999999
using namespace std;
int n,m;//n表示顶点个数,m表示边的条数
int dis[9999];//dis[i]表示i点当前到起始点的最短距离
int frist[9999],next[9999];//frist[i]保存i顶点的第一条边的编号 next[i]储存"编号为i的边"的"下一条边"的编号。
int visited1[9999];//标记队列中是否存在i顶点
int u[9999],v[9999],w[9999];
queue < int >q;
class linjiebiao
{
public:
void chushihua()//初始化邻接表,只需要把frist数组初值设为-1
{
memset(frist,-1,sizeof(frist));//frist[i]如果等于-1,说明这个i顶点没有出边。
}
public:
void fuzhi(int u,int v,int w,int i)//赋值操作,注意,这次必须保留边的编号(i)
{
next[i]=frist[u];
frist[u]=i;
}
};
class meat
{
public:
void hexin()
{
q.push(1);//将1号顶点放入队列中去
visited1[1]=1;//标记
dis[1]=0;
while (!q.empty())//队列不为空,就继续执行
{
int z=q.front();//z为顶点编号
int k=frist[z];//k等于z顶点的第一条边的编号
q.pop();
visited1[z]=0;
while (k!=-1)//如果k等于-1要么说明z顶点没有出边,要么说明z顶点的出边都已经访问完成了
{
if(dis[v[k]]>dis[u[k]]+w[k])//满足松弛的条件
{
dis[v[k]]=dis[u[k]]+w[k];//更新dis数组
if(!visited1[v[k]])//如果队列中没有v[k]顶点,将该顶点加入队列
{
q.push(v[k]);
visited1[v[k]]=1;//标记为存在
}
}
k=next[k];//更新k,用来访问z顶点的下一个出边
}
}
}
public:
void shuchu()//打印dis数组
{
for(int i=1;i<=n;i++)
cout<>n>>m;
linjiebiao p;
p.chushihua();
for(int i=1;i<=m;i++)//这里的i表示边的编号,在后续计算中会用到。
{
cin>>u[i]>>v[i]>>w[i];
p.fuzhi(u[i],v[i],w[i],i);//把边的编号也给传进去
}
for(int i=1;i<=n;i++)
dis[i]=INT;
meat a;
a.hexin();
a.shuchu();
}