拓扑排序,GCD,深度优先搜索(DFS)与广度优先搜索(BFS)

一:拓扑排序

题目链接:207. 课程表 - 力扣(LeetCode)

可以通过拓扑排序算法,用于判断是否可以完成所有课程的学习。算法通过以下步骤来判断:

  1. 构建有向图并计算每个节点的入度。
  2. 将所有入度为0的节点(即没有先修课程的课程)加入队列。
  3. 使用宽度优先搜索(BFS)遍历图,每次从队列中取出一个节点,并将其邻接节点的入度减1。
  4. 如果某个邻接节点的入度变为0,则将其加入队列。
  5. 最后,如果访问过的节点数量等于总课程数量,则表示可以完成所有课程;否则,存在循环依赖,无法完成所有课程。
def canFinish(numCourses, prerequisites):
    # 初始化一个列表,用于记录每门课程的入度(即需要先修的课程数量)
    in_degree = [0] * numCourses
    # 初始化一个列表的列表,用于构建课程的有向图
    graph = [[] for _ in range(numCourses)]

    # 遍历先修课程列表,构建有向图并计算每门课程的入度
    for course, prereq in prerequisites:
        graph[prereq].append(course)  # 在图中添加边,从先修课程指向当前课程
        in_degree[course] += 1  # 增加当前课程的入度

    # 初始化一个队列,用于存储所有入度为0的课程(即没有先修课程的课程)
    queue = [i for i in range(numCourses) if in_degree[i] == 0]

    # 初始化一个变量,用于记录已访问的课程数量
    visited_courses = 0

    # 当队列不为空时,进行循环
    while queue:
        # 从队列中取出一个课程
        course = queue.pop(0)
        # 增加已访问的课程数量
        visited_courses += 1

        # 遍历当前课程的邻接课程(即依赖于当前课程的其他课程)
        for neighbor in graph[course]:
            # 减少邻接课程的入度,因为当前课程已经完成
            in_degree[neighbor] -= 1
            # 如果邻接课程的入度变为0,说明它没有其他先修课程了,可以加入队列
            if in_degree[neighbor] == 0:
                queue.append(neighbor)

    # 如果已访问的课程数量等于总课程数量,则返回True,表示可以完成所有课程
    # 否则返回False,表示存在循环依赖,无法完成所有课程
    return visited_courses == numCourses

二:GCD,深度优先搜索(DFS)与广度优先搜索(BFS)

题目链接:365. 水壶问题 - 力扣(LeetCode)

1:GCD

可以通过检查水壶的总容量与目标量的关系,以及利用最大公约数的性质,来判断是否可以通过给定的操作得到目标量的水。

def canMeasureWater(x, y, target):
    # 如果两个水壶的容量之和小于目标量,则无法实现
    if x + y < target:
        return False
    # 如果任一水壶的容量为0,则只有当目标量为0或者两水壶容量之和等于目标量时,才能实现
    if x == 0 or y == 0:
        return target == 0 or x + y == target
    # 使用最大公约数(GCD)的性质来判断是否能够实现目标量
    # 如果x和y的最大公约数能够整除目标量,则可以实现
    return target % gcd(x, y) == 0

def gcd(a, b):
    # 辗转相除法计算最大公约数
    while b != 0:
        a, b = b, a % b
    return a

这个函数canMeasureWater用于判断是否可以通过装满、清空或倒转两个水壶的操作,最终得到目标量的水。 它首先处理了两个基本情况:如果两水壶容量之和小于目标量,或者任一水壶容量为0,则直接返回结果。 接着,它利用最大公约数的性质来判断是否能通过操作两个水壶得到目标量。 gcd函数则用于计算两个数的最大公约数,采用辗转相除法。

这个问题的解决方案基于数学中的一个重要性质,即贝祖定理(也称为扩展欧几里得算法)。以下是为什么可以使用最大公约数(GCD)来判断是否可以得到目标量的水的原因:

  1. 贝祖定理:对于任意两个整数a和b,如果存在整数x和y使得ax + by = gcd(a, b),那么ax + by的任何倍数也可以表示为两个整数的线性组合。在这个问题中,a和b分别是两个水壶的容量x和y。

  2. 水壶操作的本质:水壶操作(装满、清空、倒转)可以看作是对水壶中水量的整数加减。例如,如果你从一个水壶中倒出一些水到另一个水壶,你可以看作是在一个水壶中减去一定量的水,在另一个水壶中加上同样数量的水。

  3. 操作的限制:由于水壶的容量是固定的,这意味着任何操作都不会改变两个水壶容量的最大公约数。换句话说,不管你如何操作,两个水壶中水的总量总是x和y的线性组合。

  4. 达到目标量的条件:如果你想要得到目标量target,那么target必须是x和y的线性组合,即存在整数m和n,使得mx + ny = target。根据贝祖定理,如果gcd(x, y)能整除target,那么一定存在这样的整数m和n。

  5. 特殊情况:如果x或y为0,那么我们只能得到0或另一个水壶的容量作为水的总量。因此,如果target不是0并且x和y中有一个为0,那么我们只能得到target等于非零水壶的容量。

综上所述,我们可以通过检查target是否是x和y的最大公约数的倍数来确定是否可以通过操作两个水壶得到目标量的水。这是因为水壶操作的线性组合性质和贝祖定理保证了这种方法的正确性。

2:深度优先搜索(DFS)

我们可以将这个问题看作是一个有向图的问题,其中每个课程(水壶)是一个节点,如果课程A是课程B的先修课程,则存在一条从B到A的有向边。我们可以使用深度优先搜索来探索所有可能的操作组合,以查看是否可以达到目标水量。

  1. 状态表示:我们可以用一个三元组(a, b, state)来表示当前的状态,其中a和b分别是两个水壶中的水量,state是当前的操作步骤。
  2. 状态转移:在每一步,我们可以选择以下操作:装满一个水壶、清空一个水壶、将水从一个水壶倒入另一个水壶。
  3. 终止条件:如果任一水壶中的水量等于目标水量,或者我们探索了所有可能的状态但没有找到解决方案,则停止搜索。
def canMeasureWaterDFS(x, y, target):
    # 初始化一个集合,用于记录已经访问过的状态
    visited = set()

    # 定义一个嵌套的DFS函数,用于递归地探索所有可能的状态
    def dfs(a, b):
        # 如果当前状态已经被访问过,则返回False
        if (a, b) in visited:
            return False
        # 如果当前任一水壶的水量等于目标水量,或者两个水壶的水量之和等于目标水量,则返回True
        if a == target or b == target or a + b == target:
            return True
        # 将当前状态添加到已访问集合中
        visited.add((a, b))
        # 尝试执行以下操作,并递归地调用dfs函数:
        # 1. 装满x水壶
        # 2. 装满y水壶
        # 3. 清空x水壶
        # 4. 清空y水壶
        # 5. 将x水壶的水倒入y水壶,直到y水壶满或x水壶空
        # 6. 将y水壶的水倒入x水壶,直到x水壶满或y水壶空
        # 如果任一操作能成功达到目标,则返回True
        return (dfs(x, b) or dfs(a, y) or dfs(0, b) or dfs(a, 0) or
                dfs(a - min(a, y - b), b + min(a, y - b)) or
                dfs(a + min(b, x - a), b - min(b, x - a)))

    # 从初始状态(0, 0)开始递归搜索
    return dfs(0, 0)

该函数使用深度优先搜索(DFS)算法来探索所有可能的水壶操作组合,以判断是否能达到目标水量。算法通过递归地尝试装满、清空和倒水操作,来遍历所有可能的状态。使用一个集合来记录已经访问过的状态,以避免重复搜索。 当任一水壶的水量等于目标水量,或者两个水壶的水量之和等于目标水量时,算法返回True。如果所有可能的状态都被探索过且没有找到解决方案,则算法返回False。

3:广度优先搜索(BFS)

与深度优先搜索类似,我们也可以使用广度优先搜索来解决这个问题。广度优先搜索更适合这种问题,因为它可以找到到达目标状态的最短路径。

  1. 状态表示:与方法一相同,使用三元组(a, b, state)来表示状态。
  2. 状态转移:与方法一相同,执行装满、清空和倒水的操作。
  3. 队列操作:使用队列来存储待探索的状态。每次从队列中取出一个状态,执行所有可能的操作,并将新状态加入队列。
from collections import deque

def canMeasureWaterBFS(x, y, target):
    # 初始化一个双端队列,用于广度优先搜索,并加入初始状态(0, 0)
    queue = deque([(0, 0)])
    # 初始化一个集合,用于记录已经访问过的状态,并加入初始状态(0, 0)
    visited = set((0, 0))

    # 当队列不为空时,继续搜索
    while queue:
        # 从队列中取出当前状态的水量a和b
        a, b = queue.popleft()
        # 如果任一水壶的水量等于目标水量,或者两个水壶的水量之和等于目标水量,则返回True
        if a == target or b == target or a + b == target:
            return True
        # 定义所有可能的操作,并生成新的状态
        states = [
            (x, b),  # 装满x水壶
            (a, y),  # 装满y水壶
            (0, b),  # 清空x水壶
            (a, 0),  # 清空y水壶
            # 将x水壶的水倒入y水壶,直到y水壶满或x水壶空
            (min(a + b, x), b - (min(a + b, x) - a)),
            # 将y水壶的水倒入x水壶,直到x水壶满或y水壶空
            (a + (min(a + b, y) - b), min(a + b, y))
        ]
        # 遍历所有新状态
        for state in states:
            # 如果状态未被访问过,则加入队列和已访问集合
            if state not in visited:
                queue.append(state)
                visited.add(state)
    # 如果所有状态都已探索且没有找到解决方案,则返回False
    return False

该函数使用广度优先搜索(BFS)算法来探索所有可能的水壶操作组合,以判断是否能达到目标水量。算法通过使用队列按层次遍历所有可能的状态,并尝试装满、清空和倒水操作。使用一个集合来记录已经访问过的状态,以避免重复搜索。当任一水壶的水量等于目标水量,或者两个水壶的水量之和等于目标水量时,算法返回True。如果所有可能的状态都被探索过且没有找到解决方案,则算法返回False。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值