最短路算法 :Bellman-ford算法 & Dijkstra算法 & floyd算法 & SPFA算法详解&BFS
2016-03-12 21:38
495 查看
1、Bellman-Ford算法
2、Dijkstra算法(代码 以邻接矩阵为例) && Dijkstra + 优先队列的优化(也就是堆优化)
3、floyd-Warshall算法(代码 以邻接矩阵为例)
4、SPFA(代码 以前向星为例)
5、BFS 求解最短路+路径还原
松弛操作:
源点用s表示,用数组 dis
来存储最短路径,dis
数组为源点到其他点的最小距离。那么最最开始的最短路径的估计值也就是对 dis
的初始化喽。一般我们的初始化都是初始化为 dis
= +∞ , But 在一些时候是初始化为dis
= 0的(“一些时候”后面再讲),dis[s] = 0;
Bellman-Ford算法:解决的是一般情况下的单源最短路径问题,其边可以为负值。bellman-ford算法可以判断图是否存在负环,若存在负环会返回一个布尔值。当然在没有负环存在的 情况下会返回所求的最短路径的值。算法如下:
1)图的初始化等操作
2)for i = 1 to |G.V| - 1 //|G.V| 为图 G的点的总数(图中任一点最长的路径(边数)最大为 |G.V| - 1 )
3)for each edge(u,v)∈G.E //G.E 为图 G 的边
4) relax(u,v,w) //也就是if v.d>u.d+w(u,v) , v.d = u.d+w(u,v);
5)for each edge(u,v)∈G.E
6) if v.d>u.d+w(u,v) //v.d为出发源点到结点v的最短路径的估计值 u.d亦如此 w(u,v) 为u结点到v结点的权重值(通俗点就是u—>v的花费)。
7 return false;
8 return true
此算法分为3步:
1)第1行对图进行初始化,初始化dis
= +∞,dis[s] = 0;
2) 第2~4行为求最短路的过程,是对图的每一条边进行|V|-1次松弛操作,求取最短路径。
3)第5~8行为对每条边进行|V|-1次松弛后,检查是否存在负环并返回相应的布尔值,因为进行|V|-1次松弛后若没有负环则v.d的值确定不变,若有负环则会继续进行松弛操作,因为一个数+负数是一定比它本身要小的。
此算法的 时间复杂度为O(VE)。
用到的数据结构:
当已经明确没有负环的时候:
对于优化的解释:若图中存在负环的情况下外循环需要|V|-1次循环,若不存在负环,平均情况下的循环次数是要小于|V|-1次,当所有边没有松弛操作的时候我们就得到了最后的答案,没有必要继续循环下去,So有了这个简单的优化。
当需要判断有没有负环的时候:
方法一:
方法二:
Dijkstra算法:解决的是带权重的有向图上单源最短路径问题,该算法要求所有边的权重都为正值。Dijkstra算法在运行过程中维持的关键信息是一组结点集合S。 从源结点s 到该集合中每个结点之间的最短路径都已经被找到。算法重复从结点集V-S中选择最短路径估计最小的结点u,讲u加入到 集合S,然后对所有从u发出的边进行松弛。Dijkstra 算法如下:
1 对图的建立和处理,dis
数组的初始化等等操作
2 S = ∅
3 Q = G.V
4 while Q ≠ ∅
5 u = EXTRACT-MIN(Q)
6 S = S ∪ {u}
7 for each vertex v∈ G.Adj[u]
8 relax(u,v,w)
此算法在此分为二步 : 第二大步中又分为3小步
1) 第1~3行 对dis
数组等的初始化,集合S 为∅,Q集合为G.V操作
2) 第4~8行 中,
① 第4行 进行G.V次操作
② 第5~6行 从Q中找到一个点,这个点是Q中所有的点u—>S中某点最小的最短路径的点,并将此点加入S集合
③ 第7~8行 进行松弛操作,用此点来更新与u相连的点的路径的距离。
对于邻接矩阵存储的图 来说此算法的时间复杂度为 O(|V|²),用其他的数据结构可以优化为O(|E|log|V|)的时间复杂度。
方法一:应用的数据结构:二维数组
方法二:用到的数据结构 :前向星 对组 优先队列
前向星的加边函数:
算法实现:
floyd-Warshall算法(动态规划):是一个很强大的算法,它可以计算任意两点之间的最短路径,其边可以为负值。
证明:
对于0~k,我们分i到j的最短路正好经过顶点k一次和完全不经过顶点k两种情况来讨论。不仅过顶点k的情况下,d[k][i][j] = d[k-1][i][j]。通过顶点k的情况,d[k][i][j]= d[k-1][i][k]+d[k-1][k][j]。合起来就得到了d[k][i][j] = min(d[k-1][i][j],d[k-1][i][k]+d[k-1][k][j])。这个DP也可以用同一个数组不断进行如下的操作:d[i][j] = min(d[i][j],d[i][k]+d[k][j])的更新来实现。floyd算法的时间复杂度为O(|V|³)。
补充一下:对于floyd判断负环是否存在只需检查是否存在d[i][i]是负数的顶点i 即可。
SPFA算法(bellman-ford算法的优化):设立一个先进先出的 队列 用来保存待优化的结点,优化时每次取出队首结点u,并且用u点当前的最短路径估计值对离开u点所指向的结点v进行松弛操作,如果v点的最短 路径估计值有所调整,且v点不在当前的队列中,就将v点放入队尾。这样不断从队列中取出结点来进行松弛操作,直至队列空为止。
SPFA 是这样判断负环的: 如果某个点进入队列的次数超过N次则存在负环(SPFA无法处理带负环的图)
期望的时间复杂度:O(ke), 其中k为所有顶点进队的平均次数,可以证明k一般小于等于2。
SPFA算法有两个优化算法 SLF 和 LLL: SLF:Small Label First 策略,设要加入的节点是j,队首元素为i,若dist(j)<
dist(i),则将j插入队首,否则插入队尾。 LLL:Large Label Last 策略,设队首元素为i,队列中所有dist值的平均值为x,若dist(i)>x则将i插入到队尾,查找下一元素,直到找到某一i使得dist(i)<=x,则将i出队进行松弛操作。 SLF 可使速度提高 15 ~ 20%;SLF + LLL 可提高约 50%。 在实际的应用中SPFA的算法时间效率不是很稳定 ,为了避免最坏情况的出现,通常使用效率更加稳定的Dijkstra算法。
1 对图的建立和处理,dis
数组的初始化等等操作
2 Q += s //Q 为一个队列 s为源点
3 while Q ≠ ∅//队列不为空
4 u = Q中的点//从Q中取出一个点u
5 把u点标记为为访问过的
6 for each vertex v∈ G.Adj[u]//对所有的边
7 relax(u,v,w)//进行松弛
8 if(v 未被访问过)//若v未被访问过
9 Q += v;//加入队列
此算法分为3部分 :
1) 第1~2行 建图对dis
和vis
数组等数组进行初始化。 若判断负环需要加一个flag
数组,初始化为0,某点 u 若加入Q队列一次,怎flag[u]++,若flag[u]>=n,说明u进入队列的次数大于点的个数,因此此图存在负环,返回一个布尔值。
2) 第3行当队列不为空的时候进行操作
3) 第4~9行 取出Q中的点u ,用u对所有的边进行松弛操作,若松弛成功,判断该点v是否被访问过,若未访问过加入Q队列中。
所用数据结构:前向星 双向队列
算法实现:
BFS 求解最短路+路径打印:采用邻接表或前向星进行图的存储 , 则BFS的时间复杂度为开始的初始化O(V)+BFS操作O(E) = O (V+E)
所用数据结构:前向星 优先队列
算法实现:
2、Dijkstra算法(代码 以邻接矩阵为例) && Dijkstra + 优先队列的优化(也就是堆优化)
3、floyd-Warshall算法(代码 以邻接矩阵为例)
4、SPFA(代码 以前向星为例)
5、BFS 求解最短路+路径还原
松弛操作:
if(dis[i]>dis[k]+G[k][i])//其中dis[i]是其他的路径,dis[k]是现在的最小路径,G[k][i]是现在的最小路径的点到其他路径点的权值。 { dis[i] = dis[k]+G[k][i]; }
源点用s表示,用数组 dis
来存储最短路径,dis
数组为源点到其他点的最小距离。那么最最开始的最短路径的估计值也就是对 dis
的初始化喽。一般我们的初始化都是初始化为 dis
= +∞ , But 在一些时候是初始化为dis
= 0的(“一些时候”后面再讲),dis[s] = 0;
fill(dis,dis+n,MAX);//不知此函数的可以百度 dis[s] = 0;
Bellman-Ford算法:解决的是一般情况下的单源最短路径问题,其边可以为负值。bellman-ford算法可以判断图是否存在负环,若存在负环会返回一个布尔值。当然在没有负环存在的 情况下会返回所求的最短路径的值。算法如下:
1)图的初始化等操作
2)for i = 1 to |G.V| - 1 //|G.V| 为图 G的点的总数(图中任一点最长的路径(边数)最大为 |G.V| - 1 )
3)for each edge(u,v)∈G.E //G.E 为图 G 的边
4) relax(u,v,w) //也就是if v.d>u.d+w(u,v) , v.d = u.d+w(u,v);
5)for each edge(u,v)∈G.E
6) if v.d>u.d+w(u,v) //v.d为出发源点到结点v的最短路径的估计值 u.d亦如此 w(u,v) 为u结点到v结点的权重值(通俗点就是u—>v的花费)。
7 return false;
8 return true
此算法分为3步:
1)第1行对图进行初始化,初始化dis
= +∞,dis[s] = 0;
2) 第2~4行为求最短路的过程,是对图的每一条边进行|V|-1次松弛操作,求取最短路径。
3)第5~8行为对每条边进行|V|-1次松弛后,检查是否存在负环并返回相应的布尔值,因为进行|V|-1次松弛后若没有负环则v.d的值确定不变,若有负环则会继续进行松弛操作,因为一个数+负数是一定比它本身要小的。
此算法的 时间复杂度为O(VE)。
用到的数据结构:
struct Edge 2 { 3 int u, v, w;//u 为起点,v为终点,w为u—>v的权值 4 }edge[maxn;
当已经明确没有负环的时候:
void bellman_ford() { bool flag;//用于优化的 int dis[maxn];//保存最短路径 //初始化 fill(dis,dis+n,INF);//其他点为+∞ dis[s] = 0;//源点初始化为0 //m = m<<1;//若为无向图,边的数量应该变为2倍 for(int i=1;i<n;i++)//进行|V|-1次 { flag = false;//刚刚开始标记为假 for(int j=0;j<m;j++)//对每个边 { //if (v.d>u.d+w(u,v)) if(dis[edge[j].u]>dis[edge[j].v]+edge[j].w)//进行松弛操作 { dis[edge[j].u] = dis[edge[j].v]+edge[j].w;//松弛成功 flag = true;//若松弛成功则标记为真 } } if(!flag)//若所有的边i的循环中没有松弛成功的 break;//退出循环 //此优化可以大大提高效率。 } printf("%d\n",dis[t]==INF?-1:dis[t]);//输出结果 }
对于优化的解释:若图中存在负环的情况下外循环需要|V|-1次循环,若不存在负环,平均情况下的循环次数是要小于|V|-1次,当所有边没有松弛操作的时候我们就得到了最后的答案,没有必要继续循环下去,So有了这个简单的优化。
当需要判断有没有负环的时候:
方法一:
bool bellman_ford(int s) { bool flag; int dis[maxn];//保存最短路径 fill(dis,dis+n,INF);//初始化 dis[s] = 0; for(int i=1;i<n;i++)//共需进行|V|-1次 { flag = false;//优化 初始化为假 for(int j=0;j<m;j++)//对每一条边 { // if u.d>v.d+w(u,v) , u.d = v.d+w(u,v); if(dis[edge[j].u]>dis[edge[j].v]+edge[j].w)//进行松弛 { dis[edge[j].u] = dis[edge[j].v]+edge[j].w;//松弛操作成功 flag = true;//松弛成功变为真 } } if(!flag)//若每条边没有松弛 break;//跳出循环 } // 一下部分为 3) 第5~8行的操作 for(int i=0;i<m;i++) if(dis[edge[i].u]>dis[edge[i].v]+edge[i].w)//进行|V|-1次操作后 有边还能进行松弛 说明 return true;//存在负环 return false;//不存在负环 }
方法二:
bool bellman_ford() { bool flag; int dis[maxn];//保存最短路径 fill(dis,dis+n,INF);//初始化 dis[s] = 0; int i; for(i=0;i<n;i++)//共需进行|V|-1次 { flag = false;//优化 初始化为假 for(int j=0;j<m;j++)//对每一条边 { // if u.d>v.d+w(u,v) , u.d = v.d+w(u,v); if(dis[edge[j].u]>dis[edge[j].v]+edge[j].w)//进行松弛 { dis[edge[j].u] = dis[edge[j].v]+edge[j].w;//松弛操作成功 flag = true;//松弛成功变为真 } } if(!flag)//若每条边没有松弛 break;//跳出循环 //因为对于V个点 你最多需要进行|V|-1次外循环,如果有负环它会一直进行下去,但是只要进行到第V次的时候就说明存在负环了 if(i == n-1)//若有 return true;//返回有负环 } return false;//不存在负环 }
Dijkstra算法:解决的是带权重的有向图上单源最短路径问题,该算法要求所有边的权重都为正值。Dijkstra算法在运行过程中维持的关键信息是一组结点集合S。 从源结点s 到该集合中每个结点之间的最短路径都已经被找到。算法重复从结点集V-S中选择最短路径估计最小的结点u,讲u加入到 集合S,然后对所有从u发出的边进行松弛。Dijkstra 算法如下:
1 对图的建立和处理,dis
数组的初始化等等操作
2 S = ∅
3 Q = G.V
4 while Q ≠ ∅
5 u = EXTRACT-MIN(Q)
6 S = S ∪ {u}
7 for each vertex v∈ G.Adj[u]
8 relax(u,v,w)
此算法在此分为二步 : 第二大步中又分为3小步
1) 第1~3行 对dis
数组等的初始化,集合S 为∅,Q集合为G.V操作
2) 第4~8行 中,
① 第4行 进行G.V次操作
② 第5~6行 从Q中找到一个点,这个点是Q中所有的点u—>S中某点最小的最短路径的点,并将此点加入S集合
③ 第7~8行 进行松弛操作,用此点来更新与u相连的点的路径的距离。
对于邻接矩阵存储的图 来说此算法的时间复杂度为 O(|V|²),用其他的数据结构可以优化为O(|E|log|V|)的时间复杂度。
方法一:应用的数据结构:二维数组
int G[maxn][maxn];
void dijkstra() { bool vis[maxn];//相当于集合Q的功能, 标记该点是否访问过 int dis[maxn];//保存最短路径 for(int i=0;i<n;i++)//初始化 dis[i] = G[s][i];//s->其余各个点的距离 memset(vis,false,sizeof(vis));//初始化为假表示未访问过 dis[s] = 0;//s->s 距离为0 vis[s] = true;//s点访问过了,标记为真 for(int i=1;i<n;i++)//G.V-1次操作+上面对s的访问 = G.V次操作 { int k = -1; for(int j=0;j<n;j++)//从尚未访问过的点中选一个距离最小的点 if(!vis[j] && (k==-1||dis[k]>dis[j]))//未访问过 && 是距离最小的 k = j; if(k == -1)//若图是不连通的则提前结束 break;//跳出循环 vis[k] = true;//将k点标记为访问过了 for(int j=0;j<n;j++)//松弛操作 if(!vis[j] && dis[j]>dis[k]+G[k][j])//该点为访问过 && 可以进行松弛 dis[j] = dis[k]+G[k][j];//j点的距离 大于当前点的距离+w(k,j) 则松弛成功,进行更新 } printf("%d\n",dis[t]==INF?-1:dis[t]);//输出结果 }
方法二:用到的数据结构 :前向星 对组 优先队列
typedef pair<int, int >P; //pair 的first 保存的为最短距离, second保存的为顶点编号 struct Edge//前向星存边 { int to, w;//to为到达的点, w为权重 int next;//记录下一个结构体的位置 ,就向链表的next功能是一样的 }edge[maxn];//存所有的边 int head[maxn];//和链表的头指针数组是一样的。只不过此处head[u]记录的为最后加入edge的且与u相连的边在edge的位置,即下标 priority_queue<P,vector<P>,greater<P> >que;//优先队列从小到大
前向星的加边函数:
void add(int u, int v, int w) { edge[cnt].to = v; edge[cnt].w = w; edge[cnt].next = head[u];//获得下一个结构体的位置 head[u] = cnt++;//记录头指针的下标 }
算法实现:
void dijkstra() { int dis[maxn];//最短路径数组 int v;//v保存从队列中取出的数的第二个数 ,也就是顶点的编号 priority_queue<P,vector<P>,greater<P> >que;//优先队列 从小到大 Edge e;//保存边的信息,为了书写方便 P p;//保存从队列取出的数值 fill(dis,dis+n,MAX);//初始化,都为无穷大 dis[s] = 0;//s—>s 距离为0 que.push(P(0,s));//放入距离 为0 点为s while(!que.empty()) { p = que.top();//取出队列中最短距离最小的对组 que.pop();//删除 v = p.second;//获得最短距离最小的顶点编号 if(dis[v] < p.first)//若取出的不是最短距离 continue;//则进行下一次循环 for(int i=head[v];i!=-1;i=edge[i].next)//对与此点相连的所有的点进行遍历 { e = edge[i];//为了书写的方便。 if(dis[e.to]>dis[v]+e.w)//进行松弛 { dis[e.to]=dis[v]+e.w;//松弛成功 que.push(P(dis[e.to],e.to));//讲找到的松弛成功的距离 和顶点放入队列 } } } printf("%d\n",dis[t]==INF?-1:dis[t]);//输出结果 }
floyd-Warshall算法(动态规划):是一个很强大的算法,它可以计算任意两点之间的最短路径,其边可以为负值。
证明:
对于0~k,我们分i到j的最短路正好经过顶点k一次和完全不经过顶点k两种情况来讨论。不仅过顶点k的情况下,d[k][i][j] = d[k-1][i][j]。通过顶点k的情况,d[k][i][j]= d[k-1][i][k]+d[k-1][k][j]。合起来就得到了d[k][i][j] = min(d[k-1][i][j],d[k-1][i][k]+d[k-1][k][j])。这个DP也可以用同一个数组不断进行如下的操作:d[i][j] = min(d[i][j],d[i][k]+d[k][j])的更新来实现。floyd算法的时间复杂度为O(|V|³)。
void floyd() { for(int k=0;k<n;k++) for(int i=0;i<n;i++) for(int j=0;j<n;j++) G[i][j] = min(G[i][j],G[i][k]+G[k][j]); printf("%d\n",G[s][t]==INF?-1:G[s][t]); }
补充一下:对于floyd判断负环是否存在只需检查是否存在d[i][i]是负数的顶点i 即可。
SPFA算法(bellman-ford算法的优化):设立一个先进先出的 队列 用来保存待优化的结点,优化时每次取出队首结点u,并且用u点当前的最短路径估计值对离开u点所指向的结点v进行松弛操作,如果v点的最短 路径估计值有所调整,且v点不在当前的队列中,就将v点放入队尾。这样不断从队列中取出结点来进行松弛操作,直至队列空为止。
SPFA 是这样判断负环的: 如果某个点进入队列的次数超过N次则存在负环(SPFA无法处理带负环的图)
期望的时间复杂度:O(ke), 其中k为所有顶点进队的平均次数,可以证明k一般小于等于2。
SPFA算法有两个优化算法 SLF 和 LLL: SLF:Small Label First 策略,设要加入的节点是j,队首元素为i,若dist(j)<
dist(i),则将j插入队首,否则插入队尾。 LLL:Large Label Last 策略,设队首元素为i,队列中所有dist值的平均值为x,若dist(i)>x则将i插入到队尾,查找下一元素,直到找到某一i使得dist(i)<=x,则将i出队进行松弛操作。 SLF 可使速度提高 15 ~ 20%;SLF + LLL 可提高约 50%。 在实际的应用中SPFA的算法时间效率不是很稳定 ,为了避免最坏情况的出现,通常使用效率更加稳定的Dijkstra算法。
1 对图的建立和处理,dis
数组的初始化等等操作
2 Q += s //Q 为一个队列 s为源点
3 while Q ≠ ∅//队列不为空
4 u = Q中的点//从Q中取出一个点u
5 把u点标记为为访问过的
6 for each vertex v∈ G.Adj[u]//对所有的边
7 relax(u,v,w)//进行松弛
8 if(v 未被访问过)//若v未被访问过
9 Q += v;//加入队列
此算法分为3部分 :
1) 第1~2行 建图对dis
和vis
数组等数组进行初始化。 若判断负环需要加一个flag
数组,初始化为0,某点 u 若加入Q队列一次,怎flag[u]++,若flag[u]>=n,说明u进入队列的次数大于点的个数,因此此图存在负环,返回一个布尔值。
2) 第3行当队列不为空的时候进行操作
3) 第4~9行 取出Q中的点u ,用u对所有的边进行松弛操作,若松弛成功,判断该点v是否被访问过,若未访问过加入Q队列中。
所用数据结构:前向星 双向队列
struct Edge { int to,w;//to 终点,w 权值 int next;//下一个 }edge[maxn];//前向星 int head[503];//头指针式的数组 int cnt;//下标 deque<int>que;//双向队列
算法实现:
bool SPFA() { int u, v;//u 从Q中取出的点 v找到的点 int dis[maxn];//保存最短路径 int flag[maxn];//保存某点加入队列的次数 bool vis[maxn];//标记数组 deque<int>que;//双向队列 fill(dis,dis+n+1,MAX);//初始化 memset(flag,0,sizeof(flag));//初始化 memset(vis,false,sizeof(vis));//初始化 dis[s] = 0; que.push_back(1);//将s = 1 加入队列 while(!que.empty())//当队列不为空 { u = que.front();//从队列中取出一个数 que.pop_front();//删除 vis[u] = false;//标记为未访问 for(i=head[u];i!=-1;i=edge[i].next)//对所有与该边相连的边进行查找 { v = edge[i].to;//保存点 便于操作 if(dis[v]>dis[u]+edge[i].w)//进行松弛操作 { dis[v] = dis[u]+edge[i].w;//松弛成功 if(!vis[v])//若该点未被标记 { vis[v] = true;//标记为真 flag[v]++;//该点入队列次数++ if(flag[v]>=n)//若该点进入队列次数超过n次 说明有负环 return true;//返回有负环 //一下为SLF优化 if(!que.empty() && dis[v]<dis[que.front()])//若为队列不为空 && 队列第一个点的最短距离大于当前点的最短距离 que.push_front(v);//将该点放到队首 else//不然 que.push_back(v);//放入队尾 } } } } return false;//没有负环 }
BFS 求解最短路+路径打印:采用邻接表或前向星进行图的存储 , 则BFS的时间复杂度为开始的初始化O(V)+BFS操作O(E) = O (V+E)
所用数据结构:前向星 优先队列
struct P { int v, w;//v 顶点 w 最短距离 bool operator <(const P &a)const { return a.w < w;//按w从小到大排序 } }; priority_queue<P>que;//优先队列 按w从小到大 struct Edge//前向星 { int to, w;//v 顶点 w权重 int next;//下一个位置 }edge[maxn]; int head[maxn];//头指针数组
算法实现:
void BFS() { priority_queue<P>que;//优先队列 按w从小到大 bool vis[maxn];//标记数组, 标记是否被访问过 P p, q; int v; memset(vis,false,sizeof(vis));//初始化 p.to = s;//顶点为 s p.w = 0;//距离为 0 que.push(p);//放入队列 while(!que.empty())//队列不为空 { p = que.top();//取出队列的队首 que.pop();//删除 if(p.to == t)//若找到终点 { printf("%d\n",p.w);//输出结果 return ;//返回 } vis[p.to] = true;//此点标记为访问过 for(i=head[p.to];i!=-1;i=edge[i].next)//查找与该点相连的点 { v = edge[i].to; if(vis[v] == false)//若点未被访问过 { q.to = v;//存入结构体 q.w = p.w+edge[i].w;//距离更新 que.push(q);//放入队列 } } } printf("-1\n");//若没有到达终点 输出-1 }
相关文章推荐
- C#,WPF重载tabCotrol显示部分或tabItem部分
- 为什么EIA/RS422不能进行多点通信呢?
- 【LeetCode】66. Plus One
- 数据取证中对磁盘结构和工作原理的记录
- TCP的流量控制与拥塞控制
- AlphaGo开源代码
- 第二周作业
- 0312复利计算程序
- 2.bgrewriteaof问题
- 【操作系统】 存储管理
- 第一章-熟悉Objective-C
- Android开发qq一键登录成功返回数据的要方法
- Java中有效值位数控制
- 物理地址扩展PAE
- 获得栈的任一位置的元素并删除 不破坏栈的结构
- linux下gdb调试器使用学习-01
- css3:animation
- MAC 调整Launchpad 图标大小
- 10934 - Dropping water balloons(DP)
- 数据结构与算法系列----基数排序