0x06算法设计与分析复习(二):算法设计策略-贪心法4
参考书籍:算法设计与分析——C++语言描述(第二版)
算法设计策略-贪心法
单源最短路径
问题描述
单源最短路径问题是:给定带权的有向图\(G=(V,E)\)和图中结点\(s\in V\),求从s到其余各结点的最短路径,其中s称为源点。这里所指的路径长度是指路径上的边所带的权值之和,并假定边上的权值为非负值。
贪心法求解
从源点到另一个结点的任意一条路径均可看成是问题的可行解,其中长度最小的路径就是所求的最优解,路径的长度是问题的目标函数。从源点到其余每个结点的最短路径构成了单源最短路径问题的最优解。因此问题解的形式可以认为是:\(L=(L_1,L_2,\cdots,L_{n-1})\),只要每个分量都是源点到某个结点的最短路径,L就是问题的最优解。
迪杰斯特拉(Dijkstra)提出了按路径长度的非递减次序逐一产生最短路径的算法:首先求得长度最短的一条最短路径,再求得长度次短的一条最短路径,其余类推,直到从源点到其他所有结点之间的最短路径都已被求出为止。
设\(S=\{v_0,v_1,\cdots, v_k\}\)是已求得的最短路径的结点集合,一个结点\(v_i\)属于S当且仅当从源点s到\(v_i\)的最短路径已经计算,单源最短路径的最优量度标准是:使得从s到S的所有结点的路径长度之和的增量最小。所以迪杰斯特拉算法总是在集合\(V-S\)中选择“当前最短路径”长度最小的结点加入集合S中。
迪杰斯特拉(Dijkstra)算法
设集合S存放已经求得最短路径的终点,则\(V-S\)为尚未求得最短路径的终点集合。初始状态时,集合S中只存在一个源点,设为结点s。迪杰斯特拉算法的具体做法是:首先将源点s加入到S中;在算法的每一步中,按照最短路径值的非减次序,产生下一条最短路径\(s\rightarrow t\),并将该路径的终点\(t\in V-S\)加入S中;直到\(S=V\),算法结束。
在算法执行中,一个结点\(t\in V-S\)的当前最短路径,是一条从源点s到结点t的路径,在该路径上,除结点t外,其余结点都属于S,当前最短路径是所有这些路径中的最小者。于是可将最优量度标准设计为:从\(V-S\)中选择当具有最短的“前最短路径”的结点加入集合S中。
迪杰斯特拉算法用到以下数据结构:
- 一维数组d[i]中存放从源点s到结点i的当前最短路径的长度。
- 一位整型数组path[i]存放从源点s到结点i的当前最短路径上,结点i的前一个结点。因此,从源点到结点i的路径可以根据path的反向溯源来创建。
- 一维布尔数组inS,若inS[i]为true,表示结点i在S中;否则,表示i不在\(V-S\)中。
迪杰斯特拉算法计算过程
-
求第一条最短路径
在初始状态下,集合S中只有一个源点s,\(S={s}\),所以
\[d[i]=\left\{\begin{matrix} w(s,i) &若<s,i>\in E \\ \infty &若<s,i>\notin E \end{matrix}\right. \]式中,\(w(s,i)\)是边\(<s,i>\)的权值。所以对于所有结点i,d[i]有当前最短路径的长度。
第一条最短路径是所有最短路径中的最短者,它必须只包含一条边\(<s,k>\),并满足
\[d[k]=\min\{d[i]|i\in V-\{s\}\} \] -
更新d和path
将结点k加入到S中,并对所有的\(i\in V-S\)按式\(d[k]=\text{min}\{d[i]|i\in V-\{s\}\}\)修正,即
\[d[j]=\min\{d[j],d[k]+w(k,j)\} \]式中,\(w(k,j)\)是边\(<k,j>\)的权值。
-
求下一条最短路径
在\(V-S\)中,选择具有最短的当前最短路径值的结点k,满足\(d[k]=\min \{d[i]|i\in V-S\}\)。
算法的主要步骤如下(有向图用邻接矩阵表示):
- 初始化:创建长度为n的一维数组inS、d和path,并将每个inS[i]初始化为false,d[i]为a[v][i],如果\(i\neq v\)且\(d[i]< \infty\),则\(path[i]=v\),否则\(path[i]=-1\)。
- 将源点v加入集合S中:\(inS[v]=true;d[v]=0\)。
- 使用for循环,按长度的非递减次序,依次产生n-1条最短路径:选出最小的d[k];将结点k加入集合S中,\(inS[k]=true\);使用内层for语句更新数组d和path的值,使得其始终代表当前最短路径。
//迪杰斯特拉算法
template<class T>
class MGraph
{
public:
MGraph(int mSize);
void Dijkstra(int s,T*& d, int*& path);
...
private:
int Choose(int *d,boo* s);//在一维数组d中求最小值
...
T** a;//生成二维数组a,存储图的邻接矩阵
int n;//图中顶点数
};
template<class T>
int MGraph<T>::Choose(int *d,bool *s)
{
int i,minpos;
T min;
min=INFTY;
minpos=-1;
for(i=1;i<n;i++)
if(d[i]<min && !s[i]){
min=d[i];
minpos=i;
}
return minpos;
}
template<class T>
void MGraph<T>::Dijkstra(int s,T*& d,int*& path)
{
int k,i,j;
if(s<0 || s>n-1)
throw OutofBound;
bool *inS=new bool[n];
d=new T[n];
path=new int[n];
//初始化
for(i=0;i<n;i++){
inS[i]=false;
d[i]=a[s][i];//a是有向图的邻接矩阵
if(i!=s && d[i]<INFTY)
path[i]=s;
else
path[i]=-1;
}
//将源点加入到S中
inS[s]=true;
d[s]=0;
for(i=1;i<n-1;i++){
//求n-1条最短路径
k=Choose(d,inS);//选出下一条最短路径的结点k
inS[k]=true;//将k加入到S中
for(j=0;j<n;j++){
//更新d和path的值
if(!inS[j] && d[k]+a[k][j]<d[j]){
d[j]=d[k]+a[k][j];
path[j]=k;
}
}
}
}
上述迪杰斯特拉算法的时间复杂度为\(O(n^2)\)。
算法正确性
设\(\delta(s,i)\)为从源点s到结点i的最短路径的长度。
定理:已知带权有向图\(G=(V,E)\),其源点为\(s\),迪杰斯特拉算法使得对所有\(i,i\in V-\{s\},d[i]\geq \delta(s,i)\),且一旦\(d[i]=\delta(s,i)\),它将不再改变。
定理:已知带权有向图\(G=(V,E)\),其源点为\(s\),迪杰斯特拉算法使得对所有结点i和j, \(i\in S,j\in V-S\),必有\(d[i]\leq d[j]\)。
定理:已知带非负权值的有向图\(G=(V,E)\),路径\((s=v_0,\cdots,v_i,\cdots ,v_k=t)\)是从s到\(v_k\)的一条最短路径,\(v_i\in V,0\leq i\leq k<n\),则子路径\((s=v_0,\cdots,v_i)\)和\((v_i,\cdots v_k=t)\)必定分别是从s到\(v_i\)和\(v_i\)到t的最短路径。
定理:已知带非负权值的有向图\(G=(V,E)\),其源点为s,迪杰斯特拉算法结束时,对所有结点i,有\(d[i]=\delta(s,i)\)。
磁带最优存储
单带最优存储
问题描述
设有n个程序编号分别为\(0,1,\cdots,n-1\),要存放在长度为\(L\)的磁带上,程序i在磁盘上的存储长度为\(a_i,0\leq i<n\)。假定存放在磁带上的程序随时可能被检索,且磁带在每次检索前均已倒带到最前端。那么,如果n个程序在磁带上的存放次序一维\(\gamma=(\gamma_0,\gamma_1,\cdots,\gamma_{n-1})\),则检索程序\(\gamma_k\)所需的时间\(t_k\)与\(\sum_{i=0}^k a_{\gamma_i}\)成正比。假定每个程序被检索的概率相等,则平均检索时间(mean retrieval time, MRT)定义为\(\frac{1}{n}\sum_{k=0}^{n-1}t_k\)。单带最优存储问题
是求这n个程序的一种排列,使得MRT有最小值。这也等价于求使\(D(\gamma)=\sum_{k=0}^{n-1}\sum_{i=0}^ka_{\gamma_i}\)有最小值的排列。
贪心法求解
最优量度标准:计算迄今为止已选的那部分程序的D值,选择下一个程序的标准应使得该值的增加最小。
定理:设有n个程序\(\{0,1,\cdots,n-1\}\),程序i的长度为\(a_i,0\leq i<n\),且有\(a_0\leq a_1\leq \cdots\leq a_{n-1}\),\(\gamma=(\gamma_0,\gamma_1,\cdots,\gamma_{n-1})\)是这n个作业的一种排列。那么只有当\(\gamma_j=j,0\leq j<n\)时,\(D(\gamma)=\sum_{k=0}^{n-1}\sum_{i=0}^ka_{\gamma_i}\)有最小值。
多带最优存储
问题描述
设有\(m(m>1)\)条磁带\(T_0,T_1,\cdots,T_{m-1}\)和n个程序,要求将此n个程序分配到这m条磁带上,令\(I_j(0\leq j<m)\)是存放在第j条磁带上的程序子集的某种排列,\(D(I_j)\)的定义与前面相同,那么,求m条磁带上检索一个程序的平均检索时间的最小值等于求\(\sum_{0\leq j<m}D(I_j)\)的最小值。令\(TD=\sum_{0\leq j<m}D(I_j)\)。
多带最优存储问题是求n个程序在m条磁带上的一种存储方式,使得\(TD\)有最小值。
贪心法求解
在多带情况下计算最优平均检索时间,可以先将程序按照长度的非减次序排列,即\(a_0\leq a_1\leq \cdots\leq a_{n-1}\),其中\(a_i(0\leq i<n)\)是程序i的长度。从程序0和磁带\(T_0\)开始分配,将程序0放在\(T_0\)上,程序1存放在\(T_1\)上,.......,程序员m-1存放在\(T_{m-1}\)上,接着再将程序m存放在\(T_0\)上.......。一般将程序i存放在磁带\(i\mod m\)上。在这种存储方式下,每一条磁带上的程序都是按长度非减次序排列的。
//多带最优存储
#include <iostream.h>
void Store(int n, int m)
{
int j=0;
for(int i=0;i<n;i++){
cout<<"将程序"<<i<<"加到磁带"<<j<<endl;
j=(j+1)%m;
}
cout<<endl;
}
定理:设有n个程序\({0,1,\cdots,n-1}\),程序i的长度为\(a_i(0\leq i<n)\),且有\(a_0\leq a_1\leq \cdots\leq a_{n-1}\),以上程序实现将n个程序分配到m条磁带上,且有最小TD值。

浙公网安备 33010602011771号