说明:这篇博文主要是对算法的总结,因此会引用大量的优质博文,也欢迎留言推荐好的算法博文
本文持续更新
可参考的博文—贪心算法–tattoo
可观看的视频—greedy algorithm
从上面的参考文献已经对算法有了基本的了解,现总结如下:
贪心算法: 又叫贪婪算法,在求最优解的过程中,通过将原问题分成若干子问题,对每个子问题求最优解(即选取当前状态下最好的选择(局部最优解)),并由此希望最后堆叠的结果即为原问题的最好的结果。
贪心算法(又称贪婪算法)是指,在对问题求解时,总是做出在当前看来是最好的选择。也就是说,不从整体最优上加以考虑,算法得到的是在某种意义上的局部最优解 。贪心算法不是对所有问题都能得到整体最优解,关键是贪心策略的选择。也就是说,不从整体最优上加以考虑,做出的只是在某种意义上的局部最优解—百度百科
以视频上的一个例子说明,截图可能不是很清楚
从问题描述可以看出,实际上我们只需要每次将最小的两堆果子合并即可。
算法基本如上,至于编程不是很关键。
这里我给出一个python的程序—未必很快,有更快的欢迎底下留言
def greedy(nums):
n_len = len(nums) #果子种类
sum_n = 0 #初始化总的体力消耗,初值=0
i = 0
while i < n_len-1:
nums = sorted(nums) #排序
sum_new = sum(nums[:2]) #将最少的两个种类果子相加=体力消耗
del nums[:2] #加完之后就可以删除这两种果子了
nums.append(sum_new) #将相加之后的果子加入原始果子队列中
sum_n += sum_new #计算体力消耗
i += 1
return sum_n
print(greedy([1,9,2]))
这是从牛客网看到的题,题目如下:
分析:这个问题实际上看着应该是动态规划问题,可以从动态规划上解决。当然如果从数学出发也是可以用贪心算法的。
数学分析过程:
算术-几何平均不等式
,当然,回到问题本身,由于问题要求必须是正整数,所以要么就是3要么就是2。将 x = 2 和 3 x=2和3 x=2和3带入S中,取ln,很容易得到等于3,S更大,即ln3/3>ln2/2,因此应该取3,即将绳子都剪成3的长度。
又因为m要求大于1,也就是绳子一定要剪。所以当n=2时, S S S最大值不是2,而是1;所以当n<=3时:
我们上面得到的结论是在于一定可以剪成3的情况,那么当n=4呢?,显然 2 × 2 > 3 × 1 2\times2>3\times1 2×2>3×1。所以我们要优先剪成长度为3的,直到绳子小于等于4的时候,我们在将它剪为 2 × 2 2\times2 2×2.
当n>3时,容易得到,将绳子都分成长度为3的线段,除非分到无法再分,再考虑小于3的数,如2和1.
可以总结得到,当n<=3时:
class Solution:
def cutRope(self, n):
if n <=1:
print('n must great than 1...')
elif n == 2:
return 1
elif n == 3:
return 2
elif n>3:
if n%3==0:
return 3**(n/3)
elif n%3==1:
return 3**((n-4)/3)*4
elif n%3==2:
return 3**((n-2)/3)*2
print(Solution().cutRope(8))
不用递归
class Solution:
def jumpFloorII(self, n):
return 2**(n-1) #or pow(2,n-1)
Solution().jumpFloorII(3)
用递归
class Solution1:
def jumpFloorII(self, n):
if n == 1:
return 1
else:
return 2*self.jumpFloorII(n-1)
print(Solution1().jumpFloorII(3))
在计算量大的时候,显然不用递归要快很多
这是附加的一个小节,截取了视频上的一段话。
即:我们无法做出使得未来最好的选择,但我们可以做出目前看来最好的选择,这个选择对于我们未来人生来说可能不是整体最优的,但确是局部最好的。
关于递归,在数据结构与算法一书(Michael T.Goodrich著,张晓译)的第四章有非常详细的介绍。
在笔者一个对python很全的介绍中也提到了递归(第七部分第三节),这里主要使用例子来说明。
上一节的贪心算法 1.1.3跳蛙问题也可以使用递归, 现在进一步改进跳蛙问题
问题描述为
所以最大只能一次性跳2级,参考我们上面1.1.3节的介绍,很容易就可以得到如下结论
这个由来很容易就不推导了。
现在我们首先想到的是递归,很容易编程如下:
class Solution:
def jumpFloorII(self, n):
if n == 1:
return 1
elif n == 2:
return 2
else:
return Solution().jumpFloorII(n-1)+Solution().jumpFloorII(n-2)
print(Solution().jumpFloorII(4))
但是这个算法很慢,因为 f ( n − 1 ) 和 f ( n − 2 ) f(n-1)和f(n-2) f(n−1)和f(n−2)是独立计算的,这就使得有很多重复的部分(比如 f ( n − 1 ) = f ( n − 2 ) + f ( n − 3 ) f(n-1)=f(n-2)+f(n-3) f(n−1)=f(n−2)+f(n−3),这里又计算了一遍f(n-2))
递归实际上是倒过来算的,从大往小推导。一个比较好的方法是顺着看,那么上面实际上是斐波那契数列,我们可以使用迭代来求解。
class Solution2:
def jumpFloor(self,n):
if n == 1:
return 1
elif n == 2:
return 2
else:
f1 = 1
f2 = 2
for i in range(3,n+1):
temp = f1+f2
f1 = f2
f2 = temp
return temp
print(Solution2().jumpFloor(4))
这速度就提高了很多了,(但是已经不是递归算法了,而是迭代)
当然,上面的最后赋值那部分并不一定要有第三者temp,也可以改为:
else:
f1 = 1
f2 = 2
for i in range(3,n+1):
f2,f1 = f1+f2,f2
return f2
此外,上面提到的那本书,给了一个斐波那契数列好的递归方法,(实际上跟上面的迭代一样的),python代码如下;
class Solution2:
def jumpFloor(self,n):
if n <= 1:
return (n,1) #1表示定义f(0)=1
else:
(a,b) = self.jumpFloor(n-1)
return (a+b,a)
print(Solution2().jumpFloor(8)[0]) #0表示只要a+b的结果,即最后的结果
上面之所以定义 f ( 0 ) = 1 f(0)=1 f(0)=1是因为 f ( 2 ) = f ( 2 − 1 ) + f ( 2 − 2 ) = f ( 1 ) + f ( 0 ) f(2)=f(2-1)+f(2-2)=f(1)+f(0) f(2)=f(2−1)+f(2−2)=f(1)+f(0),实际上 f ( 0 ) f(0) f(0)表示一次跳了两个台阶,所以后面剩下0个台阶,这是一种方法,算1.
至于上面的程序解释,可以参考提到的书本,或者自己理解一下,也不难。
迭代法
class Solution:
def rectCover(self, n):
# write code here
if n == 0:
return 0
if n == 1:
return 1
else:
f0 = 1
f1 = 1
for i in range(2,n+1):
f1,f0 = f0+f1,f1
return f1
递归法:
class Solution:
def rectCover(self, n):
# write code here
if n == 0:
return 0
if n == 1:
return 1
if n == 2:
return 2
else:
return Solution().rectCover(n-1)+Solution().rectCover(n-2)
递归改进版
class Solution:
def rectCover(self, n):
# write code here
if n <= 1:
return (n,1)
else:
(a,b) = Solution().rectCover(n-1)
return (a+b,a)
Solution().rectCover(3)[0]
但这个答案没法在牛客上测,因为我们需要找的是第一个输出,即a+b的那个,所以需要先实例化,但是牛客上只需要给函数。