算法最优解 算法最优解
首页
目录
赞助
GitHub (opens new window)
首页
目录
赞助
GitHub (opens new window)
  • 数据结构基础

    • 怎样准备面试中的手写算法
    • 面试中需要熟知的数组知识
    • 面试中需要熟知的字符串知识
    • 面试中需要熟知的哈希表知识
    • 链表的套路都在这里了
    • 面试中需要熟知的栈和队列
    • 面试中需要熟知的递归
    • 如何理解递归
    • 关于排序,你必须知道的
    • 二分查找,你学废了吗
    • 动态规划
    • 回溯
      • 1. 回溯算法介绍
      • 2. 回溯算法的框架模板
      • 3. 回溯算法详解
      • 4. 复杂度分析
    • kmp算法详解
  • 数组

  • 位运算

  • 动态规划

  • 图

  • 区间

  • 链表

  • 矩阵

  • 字符串

  • 树

  • 堆

  • 逻辑思维

  • 目录
  • 数据结构基础
华南溜达虎
2024-07-08
目录

回溯

# 回溯

# 1. 回溯算法介绍

回溯算法(Backtracking)通过穷举所有可能的解来寻找满足条件解。它的基本思想是从解空间的一个起始节点出发,沿着一条路径搜索解空间,当发现当前路径不可能找到满足条件的解时,就回溯到上一个节点,尝试其他的路径。重复这个过程,直到找到所有可能的解,或者解空间被完全搜索。

回溯算法的特点是它采用了递归的方式,使得代码实现相对简洁。在搜索过程中,可以通过剪枝提前排除那些明显不会得到解的路径,从而减少搜索的总量,提高算法的效率。

回溯算法常用于解决排列组合、地图着色等具有递归特性的问题。

# 2. 回溯算法的框架模板

回溯算法的基本框架如下,所有回溯相关的问题都可以基于下面的框架稍加改造来实现。

def backtrack(path, choices, result):
    if (valid path):
        result.append(path)
        return

    for (all choices):
        if (valid choice):
            path.append(choice)
            backtrack(path, choices, result)
            path.pop(choice)

# 3. 回溯算法详解

下面我们通过一个例子来深入的理解一下回溯算法。

假设有三个同学A、B、C,现在要对这三个同学进行排队,请你列出所有可能排队结果。

根据上面的回溯算法框架模板我们可以快速的写出下面的代码。

def backtrack(path, choices, results):
    if len(path) == len(choices):
        # 所有同学都已排队,将当前排列添加到结果列表中
        results.append(path.copy())
        return
    for student in choices:
        if student not in path:
            # 尝试将当前同学加入排队
            path.append(student)
            # 递归继续排列剩余同学
            backtrack(path, choices, results)
            # 回溯,移除当前同学,尝试下一个同学
            path.pop()

对应的测试代码如下。

def all_possible_queues(students):
    results = []  # 存储所有可能的排队方案
    backtrack([], students, results)
    return results

# 假设有三个学生A, B, C
students = ['A', 'B', 'C']
all_queues = all_possible_queues(students)

# 打印所有可能的排队方案
for queue in all_queues:
    print(queue)

对应的递归树如下图,红色的箭头表示做选择,蓝色的箭头表示撤销选择,叶子节点是所有可能的排队顺序。

从上图可以看出,要找到所有可能的排队顺序,我们需要遍历递归树所有的节点,这里的时间复杂度是O(3^3),这是指数级别的时间复杂度!

通常回溯算法中会有一些约束条件,约束条件又分为明确说明的约束条件和隐性的约束条件,所谓隐性的约束条件就是没有明说,但是属于不符常理的一些选择,比如,在旅行商问题中,当前路径的长度不能超过当前最短路径的长度,这就属于一个隐性的约束条件。

我们可以根据约束条件对递归树进行剪枝,从而降低回溯的时间复杂度。

比如,我们给上面的例子增加一个约束条件,A同学不能排在中间,我们给上面的代码加上约束条件。

def backtrack(path, choices, results):
    if len(path) == len(choices):
        # 所有同学都已排队,将当前排列添加到结果列表中
        results.append(path.copy())
        return
    for student in choices:
        if student not in path:
            # 剪枝
            # 如果已经有一个同学排在第一个位置,且下一个同学是A,则跳过
            if len(path) == 1 and student == 'A':
                continue
            # 尝试将当前同学加入排队
            path.append(student)
            # 递归继续排列剩余同学
            backtrack(path, choices, results)
            # 回溯,移除当前同学,尝试下一个同学
            path.pop()

对应的递归树递归树可以根据约束条件剪枝如下图。

在递归树很庞大的情况下,通过剪枝可以大大缩短搜索的时间。

# 4. 复杂度分析

对于上面的例子,由于要通过回溯算法去搜索整个递归树,在最坏的情况下,需要进行指数级别的时间复杂度,空间复杂度通常与递归的深度成正比。

上次更新: 2024/07/08, 20:40:56
动态规划
kmp算法详解

← 动态规划 kmp算法详解→

Theme by Vdoing | Copyright © 2024-2024 华南溜达虎 | MIT License
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式