零基础学贪心算法

本文在写作过程中参考了大量资料,不能一一列举,还请见谅。
贪心算法的定义:
贪心算法是指在对问题求解时,总是做出在当前看来是最好的选择。也就是说,不从整体最优上加以考虑,只做出在某种意义上的局部最优解。贪心算法不是对所有问题都能得到整体最优解,关键是贪心策略的选择,选择的贪心策略必须具备无后效性,即某个状态以前的过程不会影响以后的状态,只与当前状态有关。
解题的一般步骤是:
1.建立数学模型来描述问题;
2.把求解的问题分成若干个子问题;
3.对每一子问题求解,得到子问题的局部最优解;
4.把子问题的局部最优解合成原来问题的一个解。
如果大家比较了解动态规划,就会发现它们之间的相似之处。最优解问题大部分都可以拆分成一个个的子问题,把解空间的遍历视作对子问题树的遍历,则以某种形式对树整个的遍历一遍就可以求出最优解,大部分情况下这是不可行的。贪心算法和动态规划本质上是对子问题树的一种修剪,两种算法要求问题都具有的一个性质就是子问题最优性(组成最优解的每一个子问题的解,对于这个子问题本身肯定也是最优的)。动态规划方法代表了这一类问题的一般解法,我们自底向上构造子问题的解,对每一个子树的根,求出下面每一个叶子的值,并且以其中的最优值作为自身的值,其它的值舍弃。而贪心算法是动态规划方法的一个特例,可以证明每一个子树的根的值不取决于下面叶子的值,而只取决于当前问题的状况。换句话说,不需要知道一个节点所有子树的情况,就可以求出这个节点的值。由于贪心算法的这个特性,它对解空间树的遍历不需要自底向上,而只需要自根开始,选择最优的路,一直走到底就可以了。
话不多说,我们来看几个具体的例子慢慢理解它:
1.活动选择问题
 这是《算法导论》上的例子,也是一个非常经典的问题。有n个需要在同一天使用同一个教室的活动a1,a2,…,an,教室同一时刻只能由一个活动使用。每个活动ai都有一个开始时间si和结束时间fi 。一旦被选择后,活动ai就占据半开时间区间[si,fi)。如果[si,fi]和[sj,fj]互不重叠,ai和aj两个活动就可以被安排在这一天。该问题就是要安排这些活动使得尽量多的活动能不冲突的举行。例如下图所示的活动集合S,其中各项活动按照结束时间单调递增排序。

考虑使用贪心算法的解法。为了方便,我们用不同颜色的线条代表每个活动,线条的长度就是活动所占据的时间段,蓝色的线条表示我们已经选择的活动;红色的线条表示我们没有选择的活动。
如果我们每次都选择开始时间最早的活动,不能得到最优解:

如果我们每次都选择持续时间最短的活动,不能得到最优解:

可以用数学归纳法证明,我们的贪心策略应该是每次选取结束时间最早的活动。直观上也很好理解,按这种方法选择相容活动为未安排活动留下尽可能多的时间。这也是把各项活动按照结束时间单调递增排序的原因。

 1 #include<cstdio>
 2 #include<iostream>
 3 #include<algorithm>
 4 using namespace std;
 5 int N;
 6 struct Act
 7 {
 8     int start;
 9     int end;
10 }act[100010];
11
12 bool cmp(Act a,Act b)
13 {
14     return a.end<b.end;
15 }
16
17 int greedy_activity_selector()
18 {
19     int num=1,i=1;
20     for(int j=2;j<=N;j++)
21     {
22         if(act[j].start>=act[i].end)
23         {
24             i=j;
25             num++;
26         }
27     }
28     return num;
29 }
30
31 int main()
32 {
33     int t;
34     scanf("%d",&t);
35     while(t--)
36     {
37         scanf("%d",&N);
38         for(int i=1;i<=N;i++)
39         {
40             scanf("%lld %lld",&act[i].start,&act[i].end);
41         }
42         act[0].start=-1;
43         act[0].end=-1;
44          sort(act+1,act+N+1,cmp);
45         int res=greedy_activity_selector();
46         cout<<res<<endl;
47     }
48 }  

2.钱币找零问题
这个问题在我们的日常生活中就更加普遍了。假设1元、2元、5元、10元、20元、50元、100元的纸币分别有c0, c1, c2, c3, c4, c5, c6张。现在要用这些钱来支付K元,至少要用多少张纸币?用贪心算法的思想,很显然,每一步尽可能用面值大的纸币即可。在日常生活中我们自然而然也是这么做的。在程序中已经事先将Value按照从小到大的顺序排好。

 1 #include<iostream>
 2 #include<algorithm>
 3 using namespace std;
 4 const int N=7;
 5 int Count[N]={3,0,2,1,0,3,5};
 6 int Value[N]={1,2,5,10,20,50,100};
 7
 8 int solve(int money)
 9 {
10     int num=0;
11     for(int i=N-1;i>=0;i--)
12     {
13         int c=min(money/Value[i],Count[i]);
14         money=money-c*Value[i];
15         num+=c;
16     }
17     if(money>0) num=-1;
18     return num;
19 }
20
21 int main()
22 {
23     int money;
24     cin>>money;
25     int res=solve(money);
26     if(res!=-1) cout<<res<<endl;
27     else cout<<"NO"<<endl;
28 }

3.再论背包问题
从零开始学动态规划中我们已经谈过三种最基本的背包问题:零一背包,部分背包,完全背包。很容易证明,背包问题不能使用贪心算法。然而我们考虑这样一种背包问题:在选择物品i装入背包时,可以选择物品的一部分,而不一定要全部装入背包。这时便可以使用贪心算法求解了。计算每种物品的单位重量价值作为贪心选择的依据指标,选择单位重量价值最高的物品,将尽可能多的该物品装入背包,依此策略一直地进行下去,直到背包装满为止。在零一背包问题中贪心选择之所以不能得到最优解原因是贪心选择无法保证最终能将背包装满,部分闲置的背包空间使每公斤背包空间的价值降低了。在程序中已经事先将单位重量价值按照从大到小的顺序排好。

 1 #include<iostream>
 2 using namespace std;
 3 const int N=4;
 4 void knapsack(float M,float v[],float w[],float x[]);
 5
 6 int main()
 7 {
 8     float M=50;
 9     //背包所能容纳的重量
10     float w[]={0,10,30,20,5};
11     //每种物品的重量
12     float v[]={0,200,400,100,10};
13       //每种物品的价值
14     float x[N+1]={0};
15     //记录结果的数组
16     knapsack(M,v,w,x);
17     cout<<"选择装下的物品比例:"<<endl;
18     for(int i=1;i<=N;i++) cout<<"["<<i<<"]:"<<x[i]<<endl;
19 }
20
21 void knapsack(float M,float v[],float w[],float x[])
22 {
23     int i;
24     //物品整件被装下
25     for(i=1;i<=N;i++)
26     {
27         if(w[i]>M) break;
28         x[i]=1;
29         M-=w[i];
30     }
31     //物品部分被装下
32     if(i<=N) x[i]=M/w[i];
33 } 

4.多机调度问题
n个作业组成的作业集,可由m台相同机器加工处理。要求给出一种作业调度方案,使所给的n个作业在尽可能短的时间内由m台机器加工处理完成。作业不能拆分成更小的子作业;每个作业均可在任何一台机器上加工处理。这个问题是NP完全问题,还没有有效的解法(求最优解),但是可以用贪心选择策略设计出较好的近似算法(求次优解)。当n<=m时,只要将作业时间区间分配给作业即可;当n>m时,首先将n个作业从大到小排序,然后依此顺序将作业分配给空闲的处理机。也就是说从剩下的作业中,选择需要处理时间最长的,然后依次选择处理时间次长的,直到所有的作业全部处理完毕,或者机器不能再处理其他作业为止。如果我们每次是将需要处理时间最短的作业分配给空闲的机器,那么可能就会出现其它所有作业都处理完了只剩所需时间最长的作业在处理的情况,这样势必效率较低。在下面的代码中没有讨论n和m的大小关系,把这两种情况合二为一了。

 1 #include<iostream>
 2 #include<algorithm>
 3 using namespace std;
 4 int speed[10010];
 5 int mintime[110];
 6
 7 bool cmp( const int &x,const int &y)
 8 {
 9     return x>y;
10 }
11
12 int main()
13 {
14     int n,m;
15     memset(speed,0,sizeof(speed));
16      memset(mintime,0,sizeof(mintime));
17       cin>>n>>m;
18        for(int i=0;i<n;++i) cin>>speed[i];
19     sort(speed,speed+n,cmp);
20     for(int i=0;i<n;++i)
21     {
22         *min_element(mintime,mintime+m)+=speed[i];
23        }
24     cout<<*max_element(mintime,mintime+m)<<endl;
25 }

5.小船过河问题
POJ1700是一道经典的贪心算法例题。题目大意是只有一艘船,能乘2人,船的运行速度为2人中较慢一人的速度,过去后还需一个人把船划回来,问把n个人运到对岸,最少需要多久。先将所有人过河所需的时间按照升序排序,我们考虑把单独过河所需要时间最多的两个旅行者送到对岸去,有两种方式:
1.最快的和次快的过河,然后最快的将船划回来;次慢的和最慢的过河,然后次快的将船划回来,所需时间为:t[0]+2*t[1]+t[n-1];
2.最快的和最慢的过河,然后最快的将船划回来,最快的和次慢的过河,然后最快的将船划回来,所需时间为:2*t[0]+t[n-2]+t[n-1]。
算一下就知道,除此之外的其它情况用的时间一定更多。每次都运送耗时最长的两人而不影响其它人,问题具有贪心子结构的性质。
AC代码:

 1 #include<iostream>
 2 #include<algorithm>
 3 using namespace std;
 4
 5 int main()
 6 {
 7     int a[1000],t,n,sum;
 8     scanf("%d",&t);
 9     while(t--)
10     {
11         scanf("%d",&n);
12         sum=0;
13         for(int i=0;i<n;i++) scanf("%d",&a[i]);
14         while(n>3)
15         {
16             sum=min(sum+a[1]+a[0]+a[n-1]+a[1],sum+a[n-1]+a[0]+a[n-2]+a[0]);
17             n-=2;
18         }
19         if(n==3) sum+=a[0]+a[1]+a[2];
20         else if(n==2) sum+=a[1];
21         else sum+=a[0];
22         printf("%d\n",sum);
23     }
24 }

6.区间覆盖问题
POJ1328是一道经典的贪心算法例题。题目大意是假设海岸线是一条无限延伸的直线。陆地在海岸线的一侧,而海洋在另一侧。每一个小的岛屿是海洋上的一个点。雷达坐落于海岸线上,只能覆盖d距离,所以如果小岛能够被覆盖到的话,它们之间的距离最多为d。题目要求计算出能够覆盖给出的所有岛屿的最少雷达数目。对于每个小岛,我们可以计算出一个雷达所在位置的区间。

问题转化为如何用尽可能少的点覆盖这些区间。先将所有区间按照左端点大小排序,初始时需要一个点。如果两个区间相交而不重合,我们什么都不需要做;如果一个区间完全包含于另外一个区间,我们需要更新区间的右端点;如果两个区间不相交,我们需要增加点并更新右端点。
AC代码:

 1 #include<cmath>
 2 #include<iostream>
 3 #include<algorithm>
 4 using namespace std;
 5 struct Point
 6 {
 7     double x;
 8     double y;
 9 }point[1000];
10
11 int cmp(const void *a, const void *b)
12 {
13     return (*(Point *)a).x>(*(Point *)b).x?1:-1;
14 }
15
16 int main()
17 {
18     int n,d;
19     int num=1;
20     while(cin>>n>>d)
21     {
22         int counting=1;
23         if(n==0&&d==0) break;
24         for(int i=0;i<n;i++)
25         {
26             int x,y;
27             cin>>x>>y;
28             if(y>d)
29             {
30                 counting=-1;
31             }
32             double t=sqrt(d*d-y*y);
33             //转化为最少区间的问题
34             point[i].x=x-t;
35             //区间左端点
36             point[i].y=x+t;
37             //区间右端点
38         }
39         if(counting!=-1)
40         {
41             qsort(point,n,sizeof(point[0]),cmp);
42             //按区间左端点排序
43             double s=point[0].y;
44             //区间右端点
45             for(int i=1;i<n;i++)
46             {
47                 if(point[i].x>s)
48                 //如果两个区间没有重合,增加雷达数目并更新右端点
49                 {
50                     counting++;
51                     s=point[i].y;
52                 }
53                 else if(point[i].y<s)
54                 //如果第二个区间被完全包含于第一个区间,更新右端点
55                 {
56                     s=point[i].y;
57                 }
58             }
59         }
60         cout<<"Case "<<num<<‘:‘<<‘ ‘<<counting<<endl;
61         num++;
62     }
63 }    

7.销售比赛
在学校OJ上做的一道比较好的题,这里码一下。假设有偶数天,要求每天必须买一件物品或者卖一件物品,只能选择一种操作并且不能不选,开始手上没有这种物品。现在给你每天的物品价格表,要求计算最大收益。首先要明白,第一天必须买,最后一天必须卖,并且最后手上没有物品。那么除了第一天和最后一天之外我们每次取两天,小的买大的卖,并且把卖的价格放进一个最小堆。如果买的价格比堆顶还大,就交换。这样我们保证了卖的价格总是大于买的价格,一定能取得最大收益。

 1 #include<queue>
 2 #include<vector>
 3 #include<cstdio>
 4 #include<cstdlib>
 5 #include<cstring>
 6 #include<iostream>
 7 #include<algorithm>
 8 using namespace std;
 9 long long int price[100010],t,n,res;
10
11 int main()
12 {
13     ios::sync_with_stdio(false);
14     cin>>t;
15     while(t--)
16     {
17         cin>>n;
18         priority_queue<long long int, vector<long long int>, greater<long long int> > q;
19         res=0;
20         for(int i=1;i<=n;i++)
21         {
22             cin>>price[i];
23         }
24         res-=price[1];
25         res+=price[n];
26         for(int i=2;i<=n-1;i=i+2)
27         {
28             long long int buy=min(price[i],price[i+1]);
29             long long int sell=max(price[i],price[i+1]);
30             if(!q.empty())
31             {
32                 if(buy>q.top())
33                 {
34                     res=res-2*q.top()+buy+sell;
35                     q.pop();
36                     q.push(buy);
37                     q.push(sell);
38                 }
39                 else
40                 {
41                     res=res-buy+sell;
42                     q.push(sell);
43                 }
44             }
45             else
46             {
47                 res=res-buy+sell;
48                 q.push(sell);
49             }
50         }
51         cout<<res<<endl;
52     }
53 }

下面我们结合数据结构中的知识讲解几个例子。
8.Huffman编码
这同样是《算法导论》上的例子。Huffman编码是广泛用于数据文件压缩的十分有效的编码方法。我们可以有多种方式表示文件中的信息,如果用01串表示字符,采用定长编码表示,则需要3位表示一个字符,整个文件编码需要300000位;采用变长编码表示,给频率高的字符较短的编码,频率低的字符较长的编码,达到整体编码减少的目的,则整个文件编码需要(45×1+13×3+12×3+16×3+9×4+5×4)×1000=224000位,由此可见,变长码比定长码方案好,总码长减小约25%。

对每一个字符规定一个01串作为其代码,并要求任一字符的代码都不是其他字符代码的前缀,这种编码称为前缀码。可能无前缀码是一个更好的名字,但是前缀码是一致认可的标准术语。编码的前缀性质可以使译码非常简单:例如001011101可以唯一的分解为0,0,101,1101,因而其译码为aabe。译码过程需要方便的取出编码的前缀,为此可以用二叉树作为前缀码的数据结构:树叶表示给定字符;从树根到树叶的路径当作该字符的前缀码;代码中每一位的0或1分别作为指示某节点到左儿子或右儿子的路标。

从上图可以看出,最优前缀编码码的二叉树总是一棵完全二叉树,而定长编码的二叉树不是一棵完全二叉树。 给定编码字符集C及频率分布f,C的一个前缀码编码方案对应于一棵二叉树T。字符c在树T中的深度记为dT(c),dT(c)也是字符c的前缀码长。则平均码长定义为:

使平均码长达到最小的前缀码编码方案称为C的最优前缀码。     
Huffman编码的构造方法:先合并最小频率的2个字符对应的子树,计算合并后的子树的频率;重新排序各个子树;对上述排序后的子树序列进行合并;重复上述过程,将全部结点合并成1棵完整的二叉树;对二叉树中的边赋予0、1,得到各字符的变长编码。

POJ3253一道就是利用这一思想的典型例题。题目大意是有把一块无限长的木板锯成几块给定长度的小木板,每次锯都需要一定费用,费用就是当前锯的木板的长度。给定各个要求的小木板的长度以及小木板的个数,求最小的费用。以要求3块长度分别为5,8,5的木板为例:先从无限长的木板上锯下长度为21的木板,花费21;再从长度为21的木板上锯下长度为5的木板,花费5;再从长度为16的木板上锯下长度为8的木板,花费8;总花费=21+5+8=34。利用Huffman思想,要使总费用最小,那么每次只选取最小长度的两块木板相加,再把这些和累加到总费用中即可。为了提高效率,使用优先队列优化,并且还要注意使用long long int保存结果。
AC代码:

 1 #include<queue>
 2 #include<cstdio>
 3 #include<iostream>
 4 using namespace std;
 5
 6 int main()
 7 {
 8     long long int sum;
 9     int i,n,t,a,b;
10     while(~scanf("%d",&n))
11     {
12         priority_queue<int,vector<int>,greater<int> >q;
13         for(i=0;i<n;i++)
14         {
15             scanf("%d",&t);
16             q.push(t);
17         }
18         sum=0;
19         if(q.size()==1)
20         {
21             a=q.top();
22             sum+=a;
23             q.pop();
24         }
25         while(q.size()>1)
26         {
27             a=q.top();
28             q.pop();
29             b=q.top();
30             q.pop();
31             t=a+b;
32             sum+=t;
33             q.push(t);
34         }
35         printf("%lld\n",sum);
36     }
37 }

9.Dijkstra算法
Dijkstra算法是由E.W.Dijkstra于1959年提出,是目前公认的最好的求解最短路径的方法,使用的条件是图中不能存在负边。算法解决的是单个源点到其他顶点的最短路径问题,其主要特点是每次迭代时选择的下一个顶点是标记点之外距离源点最近的顶点,简单的说就是bfs+贪心算法的思想。

 1 #include<iostream>
 2 #include<algorithm>
 3 #define INF 1000
 4 #define MAX_V 100
 5 using namespace std;
 6
 7 int main()
 8 {
 9     int V,E;
10     int i,j,m,n;
11     int cost[MAX_V][MAX_V];
12     int d[MAX_V];
13     bool used[MAX_V];
14     cin>>V>>E;
15     fill(d,d+V+1,INF);
16     fill(used,used+V,false);
17     for(i=0;i<V;i++)
18     {
19         for(j=0;j<V;j++)
20         {
21             if(i==j) cost[i][j]=0;
22             else cost[i][j]=INF;
23         }
24     }
25     for(m=0;m<E;m++)
26     {
27         cin>>i>>j>>cost[i][j];
28         cost[j][i]=cost[i][j];
29     }
30     cin>>n;
31     d[n]=0;
32     //源点
33     while(true)
34     {
35         int v=V;
36         for(m=0;m<V;m++)
37         {
38             if((!used[m])&&(d[m]<d[v])) v=m;
39         }
40         if(v==V) break;
41         used[v]=true;
42         for(m=0;m<V;m++)
43         {
44             d[m]=min(d[m],d[v]+cost[v][m]);
45         }
46     }
47     for(i=0;i<V;i++)
48     {
49         cout<<"the shortest distance between "<<n<<" and "<<i<<" is "<<d[i]<<endl;
50     }
51 }

10.最小生成树算法
设一个网络表示为无向连通带权图G =(V, E) , E中每条边(v,w)的权为c[v][w]。如果G的子图G’是一棵包含G的所有顶点的树,则称G’为G的生成树。生成树的代价是指生成树上各边权的总和,在G的所有生成树中,耗费最小的生成树称为G的最小生成树。例如在设计通信网络时,用图的顶点表示城市,用边(v,w)的权c[v][w]表示建立城市v和城市w之间的通信线路所需的费用,最小生成树给出建立通信网络的最经济方案。

构造最小生成树的Kruskal算法和Prim算法都利用了MST(最小生成树)性质:设顶点集U是V的真子集(可以任意选取),如果(u,v)∈E为横跨点集U和V—U的边,即u∈U,v∈V- U,并且在所有这样的边中,(u,v)的权c[u][v]最小,则一定存在G的一棵最小生成树,它以(u,v)为其中一条边。

使用反证法可以很简单的证明此性质。假设对G的任意一个最小生成树T,针对点集U和V—U,(u,v)∈E为横跨这2个点集的最小权边,T不包含该最小权边<u, v>,但T包括节点u和v。将<u,v>添加到树T中,树T将变为含回路的子图,并且该回路上有一条不同于<u,v>的边<u’,v’>,u’∈U,v’∈V-U。将<u’,v’>删去,得到另一个树T’,即树T’是通过将T中的边<u’,v’>替换为<u,v>得到的。由于这2条边的耗费满足c[u][v]≤c[u’][v’],故即T’耗费≤T的耗费,这与T是任意最小生成树的假设相矛盾,从而得证。

Prim算法每一步都选择连接U和V-U的权值最小的边加入生成树。

 1 #include<iostream>
 2 #include<algorithm>
 3 #define MAX_V 100
 4 #define INF 1000
 5 using namespace std;
 6
 7 int main()
 8 {
 9     int V,E;
10     int i,j,m,n;
11     int cost[MAX_V][MAX_V];
12     int mincost[MAX_V];
13     bool used[MAX_V];
14     cin>>V>>E;
15     fill(mincost,mincost+V+1,INF);
16     fill(used,used+V,false);
17     for(i=0;i<V;i++)
18     {
19         for(j=0;j<V;j++)
20         {
21             if(i==j) cost[i][j]=0;
22             else cost[i][j]=INF;
23         }
24     }
25     for(m=0;m<E;m++)
26     {
27         cin>>i>>j>>cost[i][j];
28         cost[j][i]=cost[i][j];
29     }
30     mincost[0]=0;
31     int res=0;
32     while(true)
33     {
34         int v=V;
35         for(m=0;m<V;m++)
36         {
37             if((!used[m])&&(mincost[m]<mincost[v]))
38                 v=m;
39         }
40         if(v==V) break;
41         used[v]=true;
42         res+=mincost[v];
43         for(m=0;m<V;m++)
44         {
45             mincost[m]=min(mincost[m],cost[v][m]);
46         }
47     }
48     cout<<res<<endl;
49 }

Kruskal算法每一步直接将权值最小的不成环的边加入生成树,我们借助并查集这一数据结构可以完美实现它。

 1 #include<iostream>
 2 #include<algorithm>
 3 #define MAX_E 100
 4 using namespace std;
 5 struct edge
 6 {
 7     int u,v,cost;
 8 };
 9 int pre[MAX_E];
10 edge es[MAX_E];
11 int find(int x);
12 void initvalue(int x);
13 bool same(int x,int y);
14 void unite(int x,int y);
15 bool comp(const edge& e1,const edge& e2);
16
17 int main()
18 {
19     int V,E;
20     int i,j,m,n;
21     cin>>V>>E;
22     initvalue(V);
23     for(i=0;i<E;i++) cin>>es[i].u>>es[i].v>>es[i].cost;
24     sort(es,es+E,comp);
25     int res=0;
26     for(i=0;i<E;i++)
27     {
28         edge e=es[i];
29         if(!same(e.u,e.v))
30         {
31             unite(e.u,e.v);
32             res+=e.cost;
33         }
34     }
35     cout<<res<<endl;
36 }
37
38 bool comp(const edge& e1,const edge& e2)
39 {
40     return e1.cost<e2.cost;
41 }
42
43 void initvalue(int x)
44 {
45     for(int i=0;i<x;i++) pre[i]=i;
46 }
47
48 int find(int x)
49 {
50     int r=x;
51     while(pre[r]!=r) r=pre[r];
52     int i=x,j;
53     while(pre[i]!=r)
54     {
55         j=pre[i];
56         pre[i]=r;
57         i=j;
58     }
59     return r;
60 }
61
62 bool same(int x,int y)
63 {
64     if(find(x)==find(y)) return true;
65     else return false;
66 }
67
68 void unite(int x,int y)
69 {
70     int fx=find(x);
71     int fy=find(y);
72     if(fx!=fy) pre[fx]=fy;
73 }

关于贪心算法的基础知识就简要介绍到这里,希望能作为大家继续深入学习的基础。

时间: 2024-10-11 11:10:32

零基础学贪心算法的相关文章

[零基础学python]啰嗦的除法

除法啰嗦的,不仅是python. 整数除以整数 看官请在启动idle之后,练习下面的运算: >>> 2/5 0 >>> 2.0/5 0.4 >>> 2/5.0 0.4 >>> 2.0/5.0 0.4 看到没有?麻烦出来了,如果从小学数学知识除法,以上四个运算结果都应该是0.4.但我们看到的后三个符合,第一个居然结果是0.why? 因为,在python里面有一个规定,像2/5中的除法这样,是要取整.2除以5,商是0(整数),余数是5(整

零基础学python-5.2 表达式操作符

表达式是处理数字最基本的工具 a=1#常量 a=a+1#表达式 操作符 操作符 描述 yield 生成 器函数发送协议 lambda args:expression 生成匿名函数 x if y else z 三元表达式 x or y  逻辑或(存在短路算法) x and y 逻辑与(存在短路算法) not x 逻辑非 x in y , x not in y 成员关系 x is y ,x is not y 对象实体测试 x<y,x<=y,x>y,x>=y,x==y,x!=y 比较大小

零基础学Python应该学习哪些入门知识及学习步骤安排

众所周知,Python以优雅.简洁著称,入行门槛低,可以从事Linux运维.Python Web网站工程师.Python自动化测试.数据分析.人工智能等职位,薪资待遇呈上涨趋势.很多人都想学习Python,那么零基础学Python应该学习哪些入门知识呢? Python入门知识一:解释器. Python是一种面向对象的解释型计算机程序设计语言,因此想要学好Python你必须要了解解释器.由于Python语言从规范到解释器都是开源的,所以理论上,只要水平够高,任何人都可以编写Python解释器来执行

www808888webcom零基础学Java怎么开始?199O883661学习哪些内容?

零基础学Java怎么开始?Java要学习哪些内容?攻城狮之友 2018-11-12 17:13:34Java 语言是一门随时代迅速发展的计算机语言程序,其深刻展示了程序编写的精髓,加上其简明严谨的结构及简洁的语法编写为其将来的发展及维护提供了保护.那么零基础学Java怎么开始?Java要学习哪些内容呢? 头一阶段的Java基础 JavaEE的学习内容从计算机基本概念,DOS命令开始,入门编程语言扫盲,什么是程序,如何配置Java开发环境,Java编程的过程是怎样的,Java有什么物特点,程序是如

零基础学Python,这是阿里Python8年开发经验写给你的学习路线图

今天给大家分享一位前辈整理的一个Python web学习路线.这位前辈由于有编程基础,所以采用了自学Python的方式.学完后主要做后端开发.希望对你有所启发. 整理的一个 python web 学习路线,这基本就是笔者自学后做后端的学习路线.创一个小群,供大家学习交流聊天如果有对学python方面有什么疑惑问题的,或者有什么想说的想聊的大家可以一起交流学习一起进步呀.也希望大家对学python能够持之以恒python爱好群,如果你想要学好python最好加入一个组织,这样大家学习的话就比较方便

零基础学python-2.17 文件、open()、file()

今天我们来说说文件,以及跟文件有关的内建函数open和file 首先我们在python的根目录下建一个名为"123"的txt文本文件 文件里面我们输入一些文本 我们把新建文件与源代码都放到python根目录下面 下面我们来看看代码: handler=open("123.txt")#由于把文件跟源代码建立在python的根目录, #所以这里的路径只需打名字即可 for eachLine in handler: print(eachLine,end='') handle

零基础学python-10.2 多目标赋值与变量命名规则

1.多目标赋值 >>> a=b=c='abc' >>> a,b,c ('abc', 'abc', 'abc') >>> 2.多目标赋值与共享引用 对于不可变对象是没有问题,但是对于可变对象,这里就有问题的了 >>> a=1 >>> b=a >>> b=a+1 >>> id(a) 505991632 >>> id(b) 505991648 >>>

零基础学python-10.3 表达式

常见表达式语句: 运算 解释 spam(eggs,ham) 函数调用 spam.ham(eggs) 方法调用 spam 在交互模式解释器内打印变量 print(a,b,c,sep='') 打印操作 yield x**2 产生表达式的语句 从上面我们看到,通常在两种情况下表达式用作语句 1.调用函数与方法 2.在交互模式提示符下打印 >>> x=print('abc') abc >>> x >>> print(x) None >>> 就

零基础学python-10.4 打印

这一章节说说打印,在python中,打印与文件和流的概念紧密相连 1.文件对象方法 类似于文件写入方法,print把对象打印到stdout流,然后添加一些自动的格式化,而且在打印的过程中不需要把对象转为字符串 2.标准输出流 与标准输入流和错误流组成脚本启动时创建的3中数据连接 3.调用格式 print(objects,sep='',end='',file=sys.stdout) objects指多个对象 sep指对象间插入什么字符 end指用什么结尾 file指文本发送的地方 >>>