python算法复习(四)----贪心算法和暴力递归

# 接下来就是算法基础内容中最重要的贪心算法和暴力递归了
#
# 那么什么是贪心算法呢?
# 在某一标准下,优先考虑最满足标准的样本,最后考虑最不满足标准的样本,最终得到一个答案的算法,叫做贪心算法。
# 也就是说,不从整体最优上加以考虑,所作出的是某种意义上的局部最优解


# 先拿一个经典的会议安排问题开头吧
# 一些项目要占用一个会议室宣讲,会议室不能同时容纳两个项目的宣讲。[开始时间,结束时间]
# 给你每一个项目开始的时间和结束的时间,你来安排宣讲的日程,要求会议室进行的宣讲场次最多,返回宣讲场次


def arrange_program(programs):
    programs = sorted(programs, key=lambda x: (x[1], x[0]), reverse=False)
    start = 6
    res = []
    count = 0
    while programs != []:
        for index in range(len(programs)):
            program = programs[index]
            if start <= program[0]:
                start = program[1]
                res.append(program)
                count += 1
                while True:
                    index += 1
                    if index >= len(programs):
                        return res, count
                    if programs[index][0] >= start:
                        programs = programs[index:]
                        break
                break
    return res, count


# 分金条
# 一块金条切成两半,需要花费和长度一样的铜板。比如长度为20的金条,不管切成长度多大的两半都要花费20铜板。问:怎样分最省?
# 例:给一个数组[10,20,30],代表三个人,金条总长度为60
# 解题思路:怎么分花费最少,换个方向来看,怎么把数组里的数合成的时候合成的值最小
def divide_money(arr):
    res = 0
    while len(arr) > 1:
        arr.sort()
        a, b = arr.pop(0), arr.pop(0)
        money = a + b
        res += money
        arr.append(money)
    return res


# costs[i]代表i好项目的花费,profits[i]代表i号项目的利润,k代表最多能做多少个项目,m表示你的初始资金
# 没做完一个项目,马上能获得收益。输出最后能获得的最大钱数
# 解题思路:在能做的项目中优先做利润最高的
def most_money(costs, profits, k, m):
    programs = []
    for i in range(len(costs)):
        programs.append([costs[i], profits[i]])
    # programs=[[花费,利润]],按照利润由高到低排序,利润相同按照花费由小到大排序
    programs = sorted(programs, key=lambda x: (x[1], -x[0]), reverse=True)
    while k != 0 and len(programs) != 0:
        for index in range(len(programs)):
            program = programs[index]
            if m >= program[0]:
                m += program[1]
                programs.pop(index)
                k -= 1
                break
            if index == len(programs) - 1:
                return m
    return m


# 在一个数据流中,随时取得中位数
# 遇到这种"随时"的题目,第一时间应该想到堆结构
# 解题思路:想要得到一组数的中位数,只需要把较小的一半放到大根堆里,较大的一半放到小根堆里,需要弹出的时候返回拥有较多数据的那个堆的堆顶就行
# 这里用列表简单代替堆结构,详细的堆结构请看排序章节,用堆的时间复杂度可以将为logN,用列表则是N
def get_median():
    small_heap = []
    big_heap = []
    while True:
        num = input()
        if num.isdigit() == False:
            return
        num = int(num)
        if big_heap == []:
            big_heap.append(num)

        else:
            if num >= big_heap[0]:
                small_heap.append(num)
            else:
                big_heap.append(num)
        big_heap = sorted(big_heap, reverse=True)
        small_heap = sorted(small_heap, reverse=False)
        if len(big_heap) - len(small_heap) >= 2:
            small_heap.append(big_heap.pop(0))
        elif len(small_heap) - len(big_heap) >= 2:
            big_heap.append(small_heap.pop(0))

        print(big_heap, small_heap)
        if len(big_heap) > len(small_heap):
            print(big_heap[0])
        elif len(big_heap) < len(small_heap):
            print(small_heap[0])
        else:
            print((small_heap[0] + big_heap[0]) / 2)


'''
    下面是暴力递归内容
    其实暴力递归就是尝试:
        1.把问题转化为规模缩小的同类问题的子问题
        2.有明确的不需要继续进行递归的条件(base case)
        3.有当得到子问题结果后的决策过程
        4.不记录每一个子问题的解
'''


# n层汉诺塔问题
# 左中右三个杆子,三个杆子都可以相互移动圆盘,最左边有n层从大到小的圆盘,返回把最左边的圆盘全部移到最右边所需的步数,且全程不能让大圆盘压住小圆盘
# 解题思路:
#   要移动n层圆盘而且不能让大圆盘压住小圆盘,那么肯定是要先把最下面的移动到end.
#   要把最下面的圆盘移到end,就要先把n-1个移动到other
#   把最下面的圆盘移动到end之后,其他圆盘现在在other,要做的就是把n-1个圆盘移动到end上
def hanoi_tower1(n, start, end, other):
    if n == 1:
        print("form" + start + "to" + end)
        return
    hanoi_tower1(n - 1, start, other, end)
    hanoi_tower1(1, start, end, other)
    hanoi_tower1(n - 1, other, end, start)


# 打印一个字符串的全部子序列,包括空字符
def all_subsequence_str(chr, i, res, ans):
    if i == len(chr):
        ans.append(res)
        return
    all_subsequence_str(chr, i + 1, res + chr[i], ans)
    all_subsequence_str(chr, i + 1, res, ans)
    return ans


# 打印一个字符串的全部排列
# 要求不要出现重复排列
# 不出现重复排列的方法:
#       1.res使用set()类型
#       2.在交换前先检查曾经有没有出现过(下面使用的就是这种,这样更快)
def all_permutation_str(chr, i, res):
    chr = list(chr)
    if i == len(chr):
        res.append(chr)
        return
    isVisit = []
    for index in range(i, len(chr)):
        if chr[index] not in isVisit:
            isVisit.append(chr[index])
            chr[index], chr[i] = chr[i], chr[index]
            all_permutation_str(chr, i + 1, res)
            chr[index], chr[i] = chr[i], chr[index]
    return res


# 给定一个整型数组arr,代表数值不同的纸牌排成一条线.玩家A和玩家B依次拿走每张纸牌,规定A先拿,B后拿.但是每个玩家只能拿走最左或者最右的纸牌.请返回获胜者的分数
# 解题思路:首先要弄清楚在[L,R]上先手的人,因为抽掉了一次,所以在[L,R-1]或[L+1,R]上是后手的,所以把先后手其实可以理解为交替取
#         所以,当前取的时候,当然要让自己取的+在[L,R-1]或[L+1,R]上取得最大
#         在[L,R]上后取的时候,实际是在[L,R-1]或[L+1,R]上先取,但是哪个能取得更多并不是由后手的人决定的,所以要在这两个之间取最小值,来保证当前在取的值最大
def now_get(arr, L, R):
    if L == R:
        return arr[L]
    return max(next_get(arr, L, R - 1) + arr[R], next_get(arr, L + 1, R) + arr[L])


def next_get(arr, L, R):
    if L == R:
        return 0
    return min(now_get(arr, L, R - 1), now_get(arr, L + 1, R))


def whoiswinner(arr):
    return max(now_get(arr, 0, len(arr) - 1), next_get(arr, 0, len(arr) - 1))


# 给你一个栈,请你逆序这个栈,不能申请其他的数据结构
# 解题思路:1.想要逆序一个栈,首先要得到栈底的值.2.回溯,依次放入栈底
def get_bottom(stack):
    # 每次执行递归都先保存了栈顶
    res = stack.pop()
    if stack == []:
        return res
    bottom = get_bottom(stack)
    # 如果栈为空了就说明开始弹出的栈顶是原栈的栈底,沿路返回,把其他栈顶押回
    stack.append(res)
    # 两个return一个保证返回的是栈底,一个保证沿路返回,不会由其他参数干扰
    return bottom


def reverse_stack(stack):
    # 依次弹出栈底,直到栈为空返回,再把栈底压回
    # [3,2,1]依次弹出3,2,1,为空之后回溯压入1,2,3
    if stack == []:
        return
    value = get_bottom(stack)
    reverse_stack(stack)
    stack.append(value)
    return stack


# 规定1和A对应、2和B对应...
# 那么一个数字字符可以有多少种转化结果
def conver_numTOstring(chr, i):
    if i == len(chr):
        return 1
    if chr[i] == "0":
        return 0
    if chr[i] == "1":
        res = conver_numTOstring(chr, i + 1)
        if i < len(chr) - 1:
            res += conver_numTOstring(chr, i + 2)
        return res
    if chr[i] == "2":
        res = conver_numTOstring(chr, i + 1)
        if i < len(chr) - 1 and chr[i + 1] <= "6":
            res += conver_numTOstring(chr, i + 2)
        return res
    return conver_numTOstring(chr, i + 1)


# 背包问题
# 给定两个数组weights和values,分别代表物品的重量和价值.给定一个载重为bag的袋子,返回能装下最多的价值
# 解题思路:对每一个物品都做取舍
def knapsack(weights, values, bag, i):
    if bag < 0:
        return -1
    if i == len(weights):
        return 0
    taken = knapsack(weights, values, bag - weights[i], i + 1)
    if taken != -1:
        taken = taken + values[i]
    untaken = knapsack(weights, values, bag, i + 1)
    return max(taken, untaken)


# N皇后问题
# 在N*N的棋盘上摆上N个皇后,要求任意两个皇后之间不同行、不同列、不同斜线,给定一个n,返回有多少种摆法
# 解题思路:深度遍历查看所有的摆法,如果可行就+1.用record记录每一行摆在哪一列,
#   record[x]=y 在第x行摆在y列, 如果j==y,说明同列;如果abs(i-x)==abs(j-y),说明同斜线.
# 优化方法:用位运算替代record确定位置有效性
def nQueens(record, i, n):
    if n == i:
        return 1
    res = 0
    for j in range(n):
        if isValid(record, i, j):
            record[i] = j
            res += nQueens(record, i + 1, n)
    return res


def isValid(record, i, j):
    for k in range(i):
        if j == record[k] or abs(record[k] - j) == abs(i - k):
            return False
    return True


'''
    总结:
        1.贪心算法要从最小的问题取思考解法,找到满足局部最优的方法去尝试
        2.暴力递归要站在宏观的角度去看整个问题
'''

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Python算法复习可以从以下几个方面入手: 1.数据结构:Python中常用的数据结构有列表、元组、字典、集合等,需要掌握它们的基本操作,如增删改查等。 2.排序算法:常见的排序算法有冒泡排序、选择排序、插入排序、快速排序、归并排序等,需要了解它们的原理和实现方法。 3.查找算法:常见的查找算法有顺序查找、二分查找、哈希查找等,需要了解它们的原理和实现方法。 4.算法归是一种常见的算法思想,需要了解归的原理和实现方法,并能够熟练地运用归解决问题。 5.动态规划算法:动态规划是一种常见的算法思想,需要了解动态规划的原理和实现方法,并能够熟练地运用动态规划解决问题。 6.贪心算法贪心算法是一种常见的算法思想,需要了解贪心算法的原理和实现方法,并能够熟练地运用贪心算法解决问题。 7.图论算法:图论是一种常见的算法思想,需要了解图论的基本概念和算法,如最短路径算法、最小生成树算法等。 8.字符串算法:字符串算法是一种常见的算法思想,需要了解字符串匹配算法、编辑距离算法等。 以下是一个例子,演示如何使用Python实现冒泡排序算法: ```python def bubble_sort(arr): n = len(arr) for i in range(n): for j in range(0, n-i-1): if arr[j] > arr[j+1]: arr[j], arr[j+1] = arr[j+1], arr[j] arr = [64, 34, 25, 12, 22, 11, 90] bubble_sort(arr) print("排序后的数组:") for i in range(len(arr)): print("%d" % arr[i]) ```

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值