动态规划的思想方法与分治思想类似,都是将一个问题逐次划分为若干个子问题,然后将子问题逐一求解。然而,对于有些递归得到的子问题,却有相同的解法,而计算机却并不知道这一点,所以对于不是相互独立的子问题,分治方法将会重复计算一些相同的子问题,效率较低。所以,动态规划最主要的思想就是对于重叠性的子问题,将会从规模最小的子问题开始计算,并且用恰当数据结构存储子问题的解,已减少重复计算。以下,将会通过寻找加权有向图的的最短路径这一实例来说明动态规划问题。
设图G=(V, E)是一个带权有向图,如果把顶点集合V划分 成k个互不相交的子集Vi(2≤k≤n,1≤i≤k),使得E中的任何 一条边<u, v>,必有u∈Vi,v∈Vi+m(1≤i<k,1<i+m≤k),则 称图G为多段图,称s∈V1为源点,t∈Vk为终点。 现用动态规划的算法来实现从源点到汇点的最短路径。
首先对于一个图而言,我们可以通过邻接矩阵和邻接表两种方法来表示一个图。这里我们将构造一个链表数组作为邻接表来表示整个图。
public class Edge { //通过一个源点、一个汇点来表示一条有向边
private int head; //源点
private int tail; //汇点
private int distance; //权数
public Edge(int head, int tail, int distance) {
this.head = head;
this.tail = tail;
this.distance = distance;
}
public int getHead() {
return head;
}
public int getTail() {
return tail;
}
public int getDistance() {
return distance;
}
}
接下来我们将定义Graph类,我们通过 LinkedList< Edge>[ ]来维护一个邻接表
对于接下来的Dijkstra算法,我们创建将创建两个链表分别表示未已求得最短路径的节点集合和已已求得最短路径的节点集合;另外,distanceNum将记录每个节点的最短距离
public class Graph{
public LinkedList<Edge>[] edgeList;//邻接表
public int nodeNum;//节点总数
public int edgeNum;//边总数
public LinkedList<Integer> Q;//初始图的节点集合
public LinkedList<Integer> S;//已求得最短路径的节点集合
public int[] distanceNum;//记录源点到每个汇点的最短距离,其中下标即为汇点坐标
public Graph(int nodeNum){
this.nodeNum=nodeNum;
edgeList=new LinkedList[nodeNum];
for(int i=0;i<nodeNum;i++)
edgeList[i]=new LinkedList<Edge>();
distanceNum=new int[nodeNum];
}
public void insertEdge(Edge edge){
int head=edge.getHead();
edgeList[head].add(edge);//通过getHead()获得边的源点,且该源点即可作为邻接表的数组下标
edgeNum++;
}
}
上述insertEdge()方法会将每条边记录,并更新总的边数
public void Dijkstra(int start){
initialize(start); //初始化Dijkstra算法所需的参数
while(!Q.isEmpty()){
int temp=minNodeDistance(Q);
S.add(temp);
LinkedList<Edge> templist=(LinkedList<Edge>)edgeList[temp].clone();//浅克隆,复制一个edgeList中的Edge给一个LinkedList
if (!templist.isEmpty()){
Edge edge=templist.pop();
findNextDis(edge);
}
}
}
private int minNodeDistance(LinkedList<Integer> Q){ //找到最短距离的节点,并将它从Q中剔除
if(!Q.isEmpty())
return 0;
int min=Q.getFirst();
for(int i=0;i<nodeNum;i++) {
if (distanceNum[Q.get(i)] < distanceNum[min])
min = Q.get(i);
Q.remove(Q.indexOf(min));
}
return min;
}
private void findNextDis(Edge edge){ //传入一个边,通过边的源点距离以及边的权数来确定汇点的距离
int head=edge.getHead();
int tail=edge.getTail();
int distance=edge.getDistance();
if(distanceNum[tail]>distanceNum[head]+distance)
distanceNum[tail]=distanceNum[head]+distance;
}
以上便是Dijkstra算法,其主要步骤如下:
①创建两个链表分别表示未已求得最短路径的节点集合和已已求得最短路径的节点集合
②初始化,将源点的距离置为0,到达其他汇点的距离记为无穷大
③找到距离最短的节点并将其从Q中取出,放入S
④从上一步的节点开始遍历源点为该节点的所有边,并根据边的权数更新该边汇点的距离
⑤重复步骤三,直到Q为空