用最通俗的语言让你学会网络流

我们想象一下自来水厂到你家的水管网是一个复杂的有向图,每一节水管都有一个最大承载流量。自来水厂不放水,你家就断水了。但是就算自来水厂拼命的往管网里面注水,你家收到的水流量也是上限(毕竟每根水管承载量有限)。你想知道你能够拿到多少水,这就是一种网络流问题。

在网上找了很久资料,虽然讲解网络流的资料很多但是浅显易懂的很少(可能是我太蒻了吧),写这篇文章只希望点进来的人都能学会网络流(都能点赞)

我尽量用通俗易懂的语言讲解,同时结合图示理解。

我将讲解以下网络流算法:

  1. 最大流
  2. 最小费用最大流

先从最基础的最大流开始:

何为最大流问题?

简单来说就是水流从一个源点s通过很多路径,经过很多点,到达汇点t,问你最多能有多少水能够到达t点。

结合图示理解:

从s到t经过若干个点,若干条边,每一条边的水流都不能超过边权值(可以小于等于但不能大于),所以该图的最大流就是10+22+45=77。

如果你还是不能理解,我们就换一种说法,假设s城有inf个人想去t城,但是从s到t要经过一些城市才能到达,(以上图为例)其中s到3城的火车票还剩10张,3到t的火车票还剩15张,其他路以此类推,问最终最多能有多少人能到达t城?(假设这个地区只有火车,没有汽车飞机,步行和骑自行车会累死就不考虑了,再假设所有人都买得起火车票。)

那怎么么解决这个问题呢?

对于这个问题,刚看到时,你有什么想法?

或许你会有一个这样的思路:从s开始找所有能到达t的路径,然后找每条路径上权值最小的边(或者说能承受水流最小的管子),再进行其它操作,就能找到这张图的最大流。

然后我们有

EK(Edmond—Karp)算法。

其实还有其它算法,但由于过于复杂(作者太懒不想画图),(以作者的语文水平)很难讲的通俗易懂,就不献丑了。

这里我就不介绍那么多公式定理之类的了(为了通俗易懂,并且不把你绕晕),为方便讲解,引入一个概念:

增广路:

增广路是指从s到t的一条路,流过这条路,使得当前的流(可以到达t的人)可以增加。

那么求最大流问题可以转换为不断求解增广路的问题,并且,显然当图中不存在增广路时就达到了最大流。

具体怎么操作呢?

其实很简单,直接从s到t广搜即可,从s开始不断向外广搜,通过权值大于0的边(因为后面会减边权值,所以可能存在边权为0的边),直到找到t为止,然后找到该路径上边权最小的边,记为mi,然后最大流加mi,然后把该路径上的每一条边的边权减去mi,直到找不到一条增广路(从s到t的一条路径)为止。(为什么要用mi呢?你要争取在这条路上多走更多人,但又不能让人停在某个城市)

具体操作:

找增广路:

bool bfs(){
    queue<int>q;
    memset(inque,0,sizeof(inque));
    memset(pre,-1,sizeof(pre));
    inque[s]=1;
    q.push(s);
    while(!q.empty()){
        int u=q.front();
        q.pop();
        for(int i=head[u];i;i=node[i].next){
            int d=node[i].v;
            if(!inque[d]&&node[i].val){//node[i].val==0则已经该路径满了
            inque[d]=1;
            pre[d].v=u;
            pre[d].edge=i;
            if(d==t)return 1;
            q.push(d);
            }
        }
    }
    return 0;
}//是否有增广路 

那个pre数组是由来记录路径的,它长这样:

struct Pre{
    int v;//该点的前一个点(从起点过来)
    int edge;//与该点相连的边(靠近起点的)
}pre[101010];

然后直接累加最大流(注意加的时候要把改边边权减去相应的流量,防止一直重复加同一段流)

那代码是长这样的吗?

int EK(){
    int ans=0;
    while(bfs()){
        int mi=inf;
        for(int i=t;i!=s;i=pre[i].v){
        mi=min(mi,node[pre[i].edge].val);
        }
        for(int i=t;i!=s;i=pre[i].v){
        node[pre[i].edge].val-=mi;
        }
        ans+=mi;
    }
    return ans;
}

当然不是,万一第一次流错了使得这样的流法无法得到最大流怎么办? 还是以刚才那张图为例,为了防止你再翻上去,我直接再放一次:

如果你第一次的增广路是:s->3->5->t流量显然是10,第二次的增广路是s->4->5->t流量显然是35(因为5->t的流量有10点被3号点来的人占领了)。

而这种方案显然不如:s->4->5->t流量为45,s->3->t流量为10。

那程序怎么知道哪种方案最优?

它当然不知道,我们要把所有情况都找一遍,难道要回溯?

当然不,这里使用一种高级技巧,加反向边。什么意思?

以下图为例:

这是刚才那张图,我们在每条边都加了一条反向的,权值为0的边(红色的边)。

有什么用呢?

占空间?拖时间?显然不是。

先不管有什么用,加了反向边,我们再跑一次EK,这次,除了要给增广路上的边都减去该路上的最小流量以外,还要给反向边加上最小流量。为什么?先别管。

先找增广路,比如说我们走了s->3->5->t,那么图会变成:

再找一次增广路,这次比如说我们走s->4->5->t。

再找一次增广路,这次我们找s->4->5->3->t

好像有点奇怪。你可能会想:为什么这次搜索的时候走了反向边(红色)的边,为什么走了反向边(红色)的边会加正向边(黑色)的边?

这就好像是45个人沿着s->4->5->t的路线想去t城,到了5城却发现有10个来自3城的人先定了5->t的票!他们十分焦急,总不能让他们在5城等吧。

怎么办呢?他们通过反向边上的标记发现了那10个人是来自3城的,利用标记,他们发现那10个人可以直接从3城到t城,于是他们(利用了哆啦A梦的时光机)告诉还在3城时的那10个人可以直接走3->t这条路,然后他们就可以买到空出来的5->t的10张票了。

现在你知道反向边的作用了吧:留下一个标记,让后面的人有机会让前面的人走另一条路。

理解了反向边的作用,恭喜你已经理解了EK求解最大流算法的原理了。

下面讲解关于反向边在代码中的实现:

关于反向边在加正向边时要一起加上,边权为0,然后在寻找增广路时就没必要关注一条边是正向边还是反向边了,在统计最大流时,要把每一条经过的边的边权减去这条增广路的流(最小流量),每条经过的边的反向边(反向边的反向边是正向边,负负得正)加上这条增广路的流。

现在还有一个小问题,怎么通过一条边的编号求它的反向边的编号。

由于正向边和反向边是一起加的,所以反向边的编号与正向边的编号只相差1。

那就好办了。

如果第一条边的编号是偶数,就有

正向边的编号^1==反向边的编号;反向边的编号^1==正向边的编号。(?为什么?)

那么接下来就来证明:当正向边的编号为2n时反向边的编号为2n+1。设n的二进制表示为XXXXX,则2n==n<<1 因此2n的二进制表示:XXXXX0,而2*n+1的二进制表示:XXXXX1。 那肯定(2n)^1==2n+1;(2n+1)^1==2n。

都讲完了,那就附上完整代码:

//EK算法求解最大流
#include<iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
#include<queue>
using namespace std;
const int inf=1<<30;
int n,m,s,t;
struct Node{
    int v;
    int val;
    int next;
}node[201010];
int top=1,head[101010];//top必须从一个奇数开始,一般用-1但我不习惯,解释见下方
inline void addedge(int u,int v,int val){
    node[++top].v=v;
    node[top].val=val;
    node[top].next=head[u];
    head[u]=top;
}
inline int Read(){
    int x=0;
    char c=getchar();
    while(c>‘9‘||c<‘0‘)c=getchar();
    while(c>=‘0‘&&c<=‘9‘)x=x*10+c-‘0‘,c=getchar();
    return x;
}
int inque[101010];//点是访问过里
struct Pre{
    int v;//该点的前一个点(从起点过来)
    int edge;//与该点相连的边(靠近起点的)
}pre[101010];
inline bool bfs(){
    queue<int>q;
    memset(inque,0,sizeof(inque));
    memset(pre,-1,sizeof(pre));
    inque[s]=1;
    q.push(s);
    while(!q.empty()){
        int u=q.front();
        q.pop();
        for(int i=head[u];i;i=node[i].next){
            int d=node[i].v;
            if(!inque[d]&&node[i].val){//node[i].val==0则已经该路径满了
            pre[d].v=u;
            pre[d].edge=i;
            if(d==t)return 1;
            inque[d]=1;
            q.push(d);
            }
        }
    }
    return 0;
}//是否有增广路
int EK(){
    int ans=0;
    while(bfs()){
        int mi=inf;
        for(int i=t;i!=s;i=pre[i].v){
            mi=min(mi,node[pre[i].edge].val);//每次只能增加增广路上最小的边的权值
        }
        for(int i=t;i!=s;i=pre[i].v){
            node[pre[i].edge].val-=mi;
            node[pre[i].edge^1].val+=mi;
            //反向的边的编号是正向边的编号^1
            //这就是为什么top开始时必须是奇数
        }
        ans+=mi;
    }
    return ans;
}
int main(){
    register int i;
    n=Read(),m=Read(),s=Read(),t=Read();
    int u,v,w;
    for(i=1;i<=m;i++)
    u=Read(),v=Read(),w=Read(),addedge(u,v,w),addedge(v,u,0);
    printf("%d",EK());
    return 0;
}

题目是:【模板】网络最大流

那么这个(乱七八糟的)算法有什么用呢?

经典应用:二分图匹配。

什么意思?

给两个集合:A,B,其中A有一些元素a1,a2,a3……,B有一些元素b1,b2,b3……

其中a1想和b1,b4,……中的一个配对,a2想和b1,b250,b2500……(这些数字没什么特别含义)配对,为最优情况下能有多少组配对成功。

二分匹配模板题: 飞行员配对方案问题

怎么做呢?

其实二分匹配问题可以转换为网络流问题。

把集合A当成起点,集合B当成终点,若集合一中元素a可以对应集合二中元素b,则加一条a指向b流量为一的边

新增加两个点,s和t,s有指向所有起点的边,所有终点有指向t的边,只需计算s到t的最大流,用EK算法即可。

本题要求记录配对方案,只需在找增广路时加个标记即可。

附上代码:

#include<iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
#include<queue>
using namespace std;
int n,m;
const int mx=1<<30;
struct Node{
    int v;
    int val;
    int nxt;
}node[5010];
int top=1;
int s=1008,t=1009;
int ansk[1050];//标号为i的外籍飞行员和标号为ansk[i]-n的英国飞行员对应
struct P{
    int fa;
    int edge;
}pre[1010];//在一条增广路中,节点的前一个节点和与之相连的边
int head[1010];
int inque[1010];
inline int Read(){
    int x=0,f=1;
    char c=getchar();
    while(c>‘9‘||c<‘0‘){
        if(c==‘-‘)f=-1;
        c=getchar();
    }
    while(c>=‘0‘&&c<=‘9‘)x=x*10+c-‘0‘,c=getchar();
    return x*f;
}
inline void addedge(int u,int v,int val){
    node[++top].v=v;
    node[top].val=val;
    node[top].nxt=head[u];
    head[u]=top;
}
bool addroad(){
    memset(pre,-1,sizeof(pre));
    memset(inque,0,sizeof(inque));
    queue<int>q;
    q.push(s);
    inque[s]=1;
    while(!q.empty()){
        int u=q.front();
        q.pop();
        for(int i=head[u];i;i=node[i].nxt){
            int d=node[i].v;
            int val=node[i].val;
            if(val!=0&&inque[d]==0){
                pre[d].fa=u;
                pre[d].edge=i;
                if(d==t)return 1;
                q.push(d);
                inque[d]=1;
            }
        }
    }
    return 0;
}//寻找增广路
int EK(){
    int ans=0;
    while(addroad()){
        int mi=mx;
        for(int i=t;i!=s;i=pre[i].fa)mi=min(mi,node[pre[i].edge].val);
        for(int i=t;i!=s;i=pre[i].fa)ansk[pre[i].fa]=i,node[pre[i].edge].val-=mi,node[pre[i].edge^1].val+=mi;
        ans+=mi;
    }
    return ans;
}//EK求最大流
int main(){
    m=Read(),n=Read();
    register int i;
    //英国飞行员用n+i号点表示,外籍飞行员用i号点表示
    for(i=1;i<=n;i++)addedge(i+n,t,1),addedge(t,i+n,0);//使所有英国空军和汇点相连
    for(i=1;i<=m;i++)addedge(s,i,1),addedge(i,s,0);//使所有外籍空军和源点相连
    int u,v;
    while(1){
    u=Read(),v=Read();
    if(u==-1&&v==-1)break;
    addedge(u,v+n,1);
    addedge(v+n,u,0);
    }
    printf("%d\n",EK());
    for(i=1;i<=n;i++)if(ansk[i]!=0)printf("%d %d\n",i,ansk[i]-n);
    return 0;
}

其实相比于网络流,二分匹配还有更快的方法,匈牙利算法,这里就不详细讲解了,今天的重点是网络流。

对于这道题,网络流会超时,匈牙利算法不会超时: 【模板】二分图匹配

感兴趣的可以自行研究,还是给出代码吧:

#include<iostream>
#include<cstring>
#include<algorithm>
#include<cstdio>
using namespace std;
int n,m,e;
int to[1010][1010];
int used[1010],y[1010];//m集合中的点是否使用过,若使用过,与它匹配的是谁
inline int Read(){
    int x=0;
    char c=getchar();
    while(c>‘9‘||c<‘0‘)c=getchar();
    while(c>=‘0‘&&c<=‘9‘)x=x*10+c-‘0‘,c=getchar();
    return x;
}
int find(int x){
    for(register int i=1;i<=m;i++){
        if(to[x][i]==1&&used[i]==0){
            used[i]=1;
            if(y[i]==0||find(y[i])==1){
                y[i]=x;
            return 1;
            }
     }
    }
    return 0;
}
int findans(){
    int ans=0;
    for(register int i=1;i<=n;i++){
        memset(used,0,sizeof(used));
        ans+=find(i);
    }
    return ans;
}
int main(){
    n=Read(),m=Read(),e=Read();
    register int i;
    int u,v;
    for(i=1;i<=e;i++){
        u=Read(),v=Read();
        if(u>n)continue;
        if(v>m)continue;
        to[u][v]=1;
    }
    printf("%d",findans());
    return 0;
}

然后就是最小费用最大流了

听名字就和上面讲的差不多。

什么是最小费用最大流呢?

以下图为例

上图中没打()的为边权(最大流量),打()的为单位流量的价格

还是用刚才那种通俗易懂的解释方法

假设s城有inf个(穷)人想去t城,但是从s到t要经过一些城市才能到达,其中s到3城的火车票还剩10张票价为6元/人,3到t的火车票还剩15张票价为7元/人,其他路以此类推,问最终最多能有多少人能到达t城?

(由于是穷人)他们希望能在最多人到达t城的同时花最少的钱,问最少的钱是多少?

有了刚才学习最大流的基础,相信接下来的讲解应该很容易看懂。

还是找增广路的思想,但是这次我们找花费最小(即s到t费用最小)的增广路。

怎么实现呢?

看到那个(即s到t费用最小)了吗?

想到了什么?

最短路算法!

也就是说我们在找增广路时,把bfs换成spfa就可以了!

附上代码(相信看到这里,不用看我打的代码你都能自己打出来了):

bool spfa(){
    memset(pre,0,sizeof(pre));
    memset(dist,0x3f,sizeof(dist));
    memset(inque,0,sizeof(inque));
    queue<int>q;
    q.push(s);
    inque[s]=1;
    dist[s]=0;
    while(!q.empty()){
        int u=q.front();
        inque[u]=0;
        q.pop();
        register int i,d,w;
        for(i=head[u];i;i=node[i].next){
            d=node[i].v;
            w=node[i].w;
            if(node[i].val&&dist[d]>dist[u]+w){
            //这里dist表示从s到某个点d的单位最小费用
                dist[d]=dist[u]+w;
                pre[d].fa=u;
                pre[d].adge=i;
                if(inque[d]==0){
                    q.push(d);
                    inque[d]=1;
                }
            }
        }
    }
    return dist[t]!=0x3f3f3f3f;
}//寻找费用最小的增广路

还有一点要注意:反向边的花费为正向边的相反数。

其实很好理解,如果那几个人不走这条路,那么这条路的钱就不用花了,但是因为刚才(前面走过的增广路)走过时花了钱,这次走反向边要退钱。

然后就是计算最大流和对应的最小费用了

int EK(){//最小费用最大流的EK算法
    maxflow=0;
    cost=0;
    int mi;
    register int i;
    while(spfa()){
        mi=inf;
        for(i=t;i!=s;i=pre[i].fa)mi=min(mi,node[pre[i].adge].val);
        for(i=t;i!=s;i=pre[i].fa){
            node[pre[i].adge].val-=mi;
            node[pre[i].adge^1].val+=mi;
        }
        maxflow+=mi;
        cost+=mi*dist[t];
        //本增广路最多能流的流量*从s到t的单位费用
    }
    return maxflow;
}

这道题是 【模板】最小费用最大流

附上完整代码:

//EK算法求解最小费用最大流
#include<iostream>
#include<cstring>
#include<algorithm>
#include<cstdio>
#include<queue>
using namespace std;
int maxflow;//最大流
int cost;//最小费用
int top=1,head[5010];
const int inf=1<<30;
int dist[5010];
int inque[5010];
int n,m,s,t;
struct Node{
    int v;
    int w;//该边单位流量的费用
    int next;
    int val;//该边最大流量
}node[101100];
struct P{
    int fa;//增广路上该点的父亲
    int adge;//该点与父亲相连的边的编号
}pre[5010];
inline int Read(){
    int x=0;
    char c=getchar();
    while(c>‘9‘||c<‘0‘)c=getchar();
    while(c>=‘0‘&&c<=‘9‘)x=x*10+c-‘0‘,c=getchar();
    return x;
}
inline void addedge(int u,int v,int val,int w){
    node[++top].v=v;
    node[top].val=val;
    node[top].w=w;
    node[top].next=head[u];
    head[u]=top;
}
bool spfa(){
    memset(pre,0,sizeof(pre));
    memset(dist,0x3f,sizeof(dist));
    memset(inque,0,sizeof(inque));
    queue<int>q;
    q.push(s);
    inque[s]=1;
    dist[s]=0;
    while(!q.empty()){
        int u=q.front();
        inque[u]=0;
        q.pop();
        register int i,d,w;
        for(i=head[u];i;i=node[i].next){
            d=node[i].v;
            w=node[i].w;
            if(node[i].val>0&&dist[d]>dist[u]+w){
                dist[d]=dist[u]+w;
                pre[d].fa=u;
                pre[d].adge=i;
                if(inque[d]==0){
                    q.push(d);
                    inque[d]=1;
                }
            }
        }
    }
    return dist[t]!=0x3f3f3f3f;
}//寻找费用最小的增广路
int EK(){//最小费用最大流的EK算法
    maxflow=0;
    cost=0;
    int mi;
    register int i;
    while(spfa()){
        mi=inf;
        for(i=t;i!=s;i=pre[i].fa)mi=min(mi,node[pre[i].adge].val);
        for(i=t;i!=s;i=pre[i].fa){
            node[pre[i].adge].val-=mi;
            node[pre[i].adge^1].val+=mi;
        }
        maxflow+=mi;
        cost+=mi*dist[t];
    }
    return maxflow;
}
int main(){
    n=Read(),m=Read(),s=Read(),t=Read();
    register int i;
    int u,v,val,w;
    for(i=1;i<=m;i++){
        u=Read(),v=Read(),val=Read(),w=Read();
        addedge(u,v,val,w);
        addedge(v,u,0,-w);
    }
    printf("%d ",EK());
    printf("%d",cost);
    return 0;
}

另外,最小费用最大流还有更高效的zkw算法,由于有点复杂(作者太懒)就不讲解的,感兴趣的自行百度。

原文地址:https://www.cnblogs.com/Star-Dragon/p/9386448.html

时间: 2024-11-10 05:54:18

用最通俗的语言让你学会网络流的相关文章

没有任何编程基础可以直接学习python语言吗?学会后能够做什么?

很明确的说 python非常适合没有任何编程基础的人入门.. 目前应用最多的:全栈开发.数据分析.运维开发,今天我们就以这三个重点的岗位来做一下自学Python的规划,希望你在学之前就能有结果的来走得更远. 很多人在问,python学了之后能做些什么? 既然你没有碰过 Python ,不知编程为何物的人,我就不提编程里面的项目名了--创一个小群,供大家学习交流聊天如果有对学python方面有什么疑惑问题的,或者有什么想说的想聊的大家可以一起交流学习一起进步呀.也希望大家对学python能够持之以

通俗的语言解释面向对象

面向对象: Jeff Goodell:请你用尽量简练的语言解释一下,究竟什么是面向对象的软件? 乔布斯:对象就像人一样,也是活生生的生命.他们有知识,知道怎么完成任务:他们有记忆,可以把发生的事情记下来.而你和他们的互动并不是低层次的,你是与他们在一个高度抽象的层面上互动,就像我们现在的对话一样. 我举个例子来说明.如果我是一个“洗衣”对象,你可以把脏衣服给我,然后告诉我说:“请帮我把这些衣服洗了吧!”而我恰好知道旧金山最好的洗衣房在哪,并且 我会说英语,兜里也有美元.于是我出门打了一辆出租车,

转载:怎样用通俗的语言解释REST,以及RESTful?

作者:覃超链接:https://www.zhihu.com/question/28557115/answer/48094438来源:知乎著作权归作者所有.商业转载请联系作者获得授权,非商业转载请注明出处. URL定位资源,用HTTP动词(GET,POST,DELETE,DETC)描述操作. --- 简洁版 --- 0. REST不是"rest"这个单词,而是几个单词缩写.但即使那几个单词说出来,也无法理解在说什么 -_-!! (不是要贬低人,是我自己也理解困难):1. REST描述的是

[转载]通俗的语言解释一下什么是 RPC 框架

来自知乎:https://www.zhihu.com/question/25536695 关于RPC你的题目是RPC框架,首先了解什么叫RPC,为什么要RPC,RPC是指远程过程调用,也就是说两台服务器A,B,一个应用部署在A服务器上,想要调用B服务器上应用提供的函数/方法,由于不在一个内存空间,不能直接调用,需要通过网络来表达调用的语义和传达调用的数据. 比如说,一个方法可能是这样定义的:Employee getEmployeeByName(String fullName)那么: 首先,要解决

用通俗的例子解释OAuth和OpenID的区别【原】

什么是OAuth(Wiki) 什么是OpenID(Wiki) 详细的定义可以看wiki,下面举个例子说说我的理解 现在很多网站都可以用第三方的账号登陆,比如,现在我要登录淘宝买东西,而如果我没有淘宝的账号,我也可以用微博的账号登录,这个微博账号就是第三方账号了. OpenID强调 验证 authentication,而OAuth强调 授权 authorization. 验证就是说“我”是不是(微博用户),而授权是说“淘宝”可不可以,而可不可以的前提则是“我”是不是. 过程大概是这样 我点开微博登

《C专家编程》第三章——分析C语言的声明

前面一章我们已经说过C语言存在的一些问题和它晦涩的地方,让我们对这门神奇的语言有了更深的了解.现在这一章则集中精力来讨论C语言的声明,分为三块,首先是说明C语言声明晦涩难懂的原因和声明是如何形成的,其次就是学习怎样对C语言的声明进行分析,另外本文将详细来探讨一个分析C语言声明的工具--cdecl,分析和编写它的源代码. C语言的声明晦涩难懂这一点应该是名不虚传的,比如说下面这个声明: void (*signal(int sig, void(*func) (int)))(int); 这可不是吓人的

【二】D语言现状、基本规定和相关资源介绍

这算是本系列文章的一个序吧,主要是为以后的学习做铺垫,文本分为三个部分,第一部分是对于网上一些比较旧的资料的问题的一些更正,当然我也不可能看过所有的资料,难免会有遗漏.第二部分是D语言最基本的规定,第三部分是相关的资源 一.对于一些网上旧资料上的内容不准确之处在此指出 1.D对闭包的支持不好 目前版本支持完全的闭包,所以不用担心 2.D不支持动态链接 目前版本支持动态链接技术,但Windows平台需要注意的是D的runtime建立在dmc(一个C编译器)runtime 基础上,它与VC的二进制格

基于c语言实现的快速排序算法

题目描述:对输入的n个数进行排序并输出. 解题思路:其实是很常规的排序题,但是为了一复习,所以采用快排的方法.这里就当是快速排序的代码实现练习好了. 快速排序是非常经典的一个算法,可以在对数规模的时间下实现对一组数的排序. 其基于分治的思想,每一趟排序把原组数划分为更小的范围进行排序,并且一定有一个数会在每趟排序下放在最终的位置. 用通俗的语言描述这个过程,就是从这一个分组的两头开始向中间遍历,一边遍历一边把大的放到右边,小的放到左边. 最后当low=high时停留在pivot的位置,再将一开始

豪情-2015年阅读书籍分享[上]

2015年阅读书籍分享[上] blog 前言:最近购了个kindle,疯狂的看了几本非技术类相关的书籍,感觉不错,结合以前感觉不错的书分享一下.分享的初衷还是推荐大家跳出技术的视角,用一种符合价值观或哲学平衡角度的来看待当前的一切.更重要的开阔自己的视野,让思路更活跃.仅个人视角,不喜勿喷,当然有好书肯定推荐.好多人问我是干啥的?其实我略懂点css,js而已.本博客持续更新,敬请持续关注. 一. 历史类 二. 职场 三. 创业 四. UI 前言:最近购了个kindle,疯狂的看了几本非技术类相关