从Dijkstra算法分析动态规划与贪心策略
Dijkstra算法是动态规划还是贪心策略?
先说结论,Dijkstra属于贪心策略。 DP每一步递归是基于前一状态的"真"最优,而贪心可能是"伪"最优。贪心算法执行完成后才能知道真正的最优解,而DP其实不需要执行完就可以得到任意状态的最优解。 如果说Dijktra算法不是动规,那么如果用动规如何来"重写"Dijkstra算法呢?
Dijkstra动态规划的解决方法
Dijkstra算法解决的是单点最短路径问题,即在一个图中,给定源点s,求到这个源点的最短路径。如果使用动态规划来解决这个问题,按照Dijkstra的思路,我们可以这样写出状态转移方程:
for e in v.edges:
u = e.to
dis[v] = min(dis[u] + e.length, dis[v])
dis[v]表示节点v到源点s的最短路径,v到s的最短路径是v点所有邻点过来路径中最短的一条,这也是上述状态转移方程的含义。 只要知道v的每个邻点u的最短路径,就可以求得v的最短路径。但事实上这是不可能的,因为无法确定到达v节点时,它的每个邻点u的最短路径就已经计算出来了。 即该方程只满足最优子结构,但不满足无后效性。
Dijkstra贪心策略的理解
将图中所有节点分为两个集合S和T,S包含确定了最短路径的节点,T是尚未确定最短路径的节点集合。 初始S集合只包括源点s,s节点所有邻边中最短那条的邻点u,可以确定<s,u>是u到s的最短路径,因此u可以确定进入S集合,再对u的所有邻点作松弛操作。(松弛操作:将节点u的最短路径效果扩散至其相邻节点)。 一般化后,只有T中距离(distance)最短的点,才可以确定它的距离是最短路径。所谓贪心,就在于此,不断选择T集合中距离最短的点加入S集合,直到目标节点t加入S集合。(具体实现参见下面Dijkstra算法实现代码) 节点一经确定进入T集合,最短路径就可以计算得出,不再受之后其它节点的松弛操作影响,所以符合无效性原则,因此所得解是全局最优解。
Dijkstra源码
struct Edge
{
int to; // edge's end node
int length;
Edge(int t, int l): to(t), length(l) {}
};
vector<vector<Edge> > graph; // graph[i][j]:i is node, while j is edge
struct Point
{
int id; // id of node
int distance; // distant to source node
Point(): id(), distance() {}
Point(int i, int d): id(i), distance(d) {}
bool operator<(const Point& b) const{
return(distance > b.distance);
}
};
int Dijkstra(int s, int t)
{
//vector<int> dis(graph[s].size(), INT_MAX);
//int dis[MAXN] = {INT_MAX}; // This method only set dis[0]
int dis[MAXN]; // dis[i]: minium distance between node i and s
fill(dis, dis + MAXN, INT_MAX);
priority_queue<Point> PointQue; // BST
// init s
dis[s] = 0;
PointQue.push(Point(s, dis[s]));
//Point curr;
while(!PointQue.empty())
{
int u = PointQue.top().id;
if (u == t)
return dis[u];
int distance = PointQue.top().distance;
PointQue.pop();
for(auto v = graph[u].begin(); v != graph[u].end(); v++)
{
if(dis[(*v).to] > dis[u] + (*v).length) // avoid back and go into dead loop
{
dis[(*v).to] = dis[u] + (*v).length;
PointQue.push(Point((*v).to, dis[(*v).to]));
}
}
}
# return dis[t];
}
动规"一个模型三个特征"
最优子结构
最优子结构指的是,问题的最优解包含子问题的最优解。反过来说就是,我们可以通过子问题的最优解,推导出问题的最优解。如果我们把最优子结构,对应到我们前面定义的动态规划问题模型上,那我们也可以理解为,后面阶段的状态可以通过前面状态推导出来。
无后效性
无后效性,有两层含义,第一层含义是,在推导后面阶段状态的时候,我们只关心前面阶段的状态值,不关心这个状态是怎么一步步推导出来的。第二层含义是,某阶段状态一旦确定,就不受之后阶段的决策影响。 当前状态只与之前的状态相关,不受之后状态的影响。
重复子问题(TODO)
不同的决策序列,到达某个相同的阶段时,可能会产生重复的状态
贪心策略是动规的特例
满足无后效性的贪心策略能够得到全局最优解。