深入理解全排列算法:DFS与回溯的完美结合

全排列问题是算法中的经典问题,其目标是将一组数字的所有可能排列组合列举出来。本文将详细解析如何通过深度优先搜索(DFS)回溯法高效生成全排列,并通过模拟递归过程帮助读者彻底掌握其核心思想。


问题描述

给定一个正整数 n,生成数字 1 到 n 的所有排列。例如,当 n = 3 时,输出应为:

1 2 3
1 3 2
2 1 3
2 3 1
3 1 2
3 2 1

算法思路

1. 核心变量

  • path[N]:存储当前生成的排列。

  • dt[N]bool 数组):标记数字是否已被使用(避免重复)。

  • n:排列的长度(如 n=3 表示生成 1,2,3 的全排列)。

2. DFS递归函数

void dfs(int u) {if (u == n) {  // 终止条件:排列已填满print_permutation();  // 输出当前排列return;}for (int i = 0; i < n; i++) {if (!dt[i]) {  // 如果数字i未被使用path[u] = i;  // 选择idt[i] = true;  // 标记为已使用dfs(u + 1);   // 递归填充下一位dt[i] = false; // 回溯:恢复状态}}
}

3. 主函数

int main() {scanf("%d", &n);dfs(0);  // 从第0位开始生成排列return 0;
}

递归过程模拟(以n=2为例)

初始状态

  • n = 2(排列长度为 2,数字为 1, 2,对应内部 0, 1)。

  • path = [?, ?](未初始化)。

  • dt = [false, false](初始均未使用)。


递归调用树

第一层调用:dfs(0)
  • 当前位 u = 0(正在填充 path[0])。

  • 循环 i = 0

    1. dt[0] 为 false,选择数字 0(实际输出为 1)。

    2. 更新状态

      • path = [0, ?]

      • dt = [true, false]

    3. 递归进入 dfs(1)

第二层调用:dfs(1)
  • 当前位 u = 1(正在填充 path[1])。

  • 循环 i = 0

    • dt[0] 为 true,跳过。

  • 循环 i = 1

    1. dt[1] 为 false,选择数字 1(实际输出为 2)。

    2. 更新状态

      • path = [0, 1]

      • dt = [true, true]

    3. 递归进入 dfs(2)

第三层调用:dfs(2)
  • 终止条件u == n2 == 2),打印当前排列:

    • 输出 path[0]+1, path[1]+1 → 1 2

  • 返回上一级(回溯到 dfs(1))。

回溯到 dfs(1)
  • 恢复状态

    • dt[1] = falsedt = [true, false])。

  • 循环结束,返回上一级 dfs(0)

回溯到 dfs(0)
  • 恢复状态

    • dt[0] = falsedt = [false, false])。

  • 继续循环 i = 1

    1. dt[1] 为 false,选择数字 1(实际输出为 2)。

    2. 更新状态

      • path = [1, ?]

      • dt = [false, true]

    3. 递归进入 dfs(1)

第二层调用:dfs(1)
  • 当前位 u = 1

  • 循环 i = 0

    1. dt[0] 为 false,选择数字 0(实际输出为 1)。

    2. 更新状态

      • path = [1, 0]

      • dt = [true, true]

    3. 递归进入 dfs(2)

第三层调用:dfs(2)
  • 打印排列:path[0]+1, path[1]+1 → 2 1

  • 返回上一级(回溯到 dfs(1))。

回溯到 dfs(1)
  • 恢复 dt[0] = falsedt = [false, true])。

  • 循环结束,返回 dfs(0)

回溯到 dfs(0)
  • 恢复 dt[1] = falsedt = [false, false])。

  • 循环结束,程序终止。

最终输出

1 2 
2 1 

关键步骤总结

  1. 递归向下:逐层选择未被使用的数字,更新 path 和 dt

  2. 回溯向上:在每一层递归返回时恢复 dt 的状态,确保其他分支能正确使用数字。

  3. 终止条件:当 path 填满时立即输出结果。


递归树图示

dfs(0)
│
├─ i=0 (选1)
│  ├─ dfs(1)
│  │  ├─ i=1 (选2) → dfs(2) → 输出 [1, 2]
│  │  └─ 回溯:释放2
│  └─ 回溯:释放1
│
└─ i=1 (选2)├─ dfs(1)│  ├─ i=0 (选1) → dfs(2) → 输出 [2, 1]│  └─ 回溯:释放1└─ 回溯:释放2

关键点总结

  1. DFS的作用:递归地尝试所有可能的数字选择,直到填满整个排列。

  2. 回溯的必要性:在递归返回时恢复 dt 数组的状态,确保后续分支能正确选择数字。

  3. 时间复杂度:O(N×N!),因为共有 N! 种排列,每种排列需要 O(N) 时间生成。


优化与扩展

  1. 非递归实现:可以用栈模拟递归,避免递归深度过大(但对小规模 n 影响不大)。

  2. 字典序排列:调整循环顺序,使输出按字典序排列。

  3. 去重排列:如果输入包含重复数字,需额外判断避免重复排列。


完整代码(C语言)

 

       通过DFS和回溯的结合,我们可以高效地生成全排列。理解递归的展开与回溯是掌握该算法的关键。希望本文的逐步解析能帮助你彻底掌握这一经典问题!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/diannao/77481.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

在 Dev-C++中编译运行GUI 程序介绍(二)示例:祝福程序

在 Dev-C中编译运行GUI 程序介绍&#xff08;二&#xff09;示例&#xff1a;祝福程序 前期见&#xff1a; 在 Dev-C中编译运行GUI 程序介绍&#xff08;一&#xff09;基础 https://blog.csdn.net/cnds123/article/details/147019078 示例1、祝福程序 本文中的这个祝福程序是…

Stable Diffusion 四重调参优化——项目学习记录

学习记录还原&#xff1a;在本次实验中&#xff0c;我基于 Stable Diffusion v1.5模型&#xff0c;通过一系列优化方法提升生成图像的质量&#xff0c;最终实现了图像质量的显著提升。实验从基础的 Img2Img 技术入手&#xff0c;逐步推进到参数微调、DreamShaper 模型和 Contro…

Solidity智能合约漏洞类型与解题思路指南

一、常见漏洞类型与通俗解释 1. 重入攻击(Reentrancy) 🌀 通俗解释:就像你去银行取钱,柜台人员先给你钱,然后再记账。你拿到钱后立即又要求取钱,由于账还没记,柜台又给你一次钱,这样循环下去你就能拿走银行所有的钱。 漏洞原理:合约在更新状态前调用外部合约,允许…

Docker部署.NetCore8项目

在VS.net新建.netCore8项目&#xff0c;生成项目的发布文件&#xff0c;之后添加Dockerfile&#xff0c;内容如下&#xff1a; FROM mcr.microsoft.com/dotnet/aspnet:8.0 # 设置工作目录 WORKDIR /app # 挂载临时卷&#xff08;类似于 VOLUME /tmp&#xff09; VOLUME /tmp …

【C++】右值引用、移动语义与完美转发

左值、右值是C常见的概念&#xff0c;那么什么是右值引用&#xff0c;移动语义&#xff0c;完美转发呢&#xff1f;本UP带大家了解一下C校招常问的C11新特性。 左值与右值 左值&#xff1a;明确存储未知、可以取地址的表达式 右值&#xff1a;临时的、即将被销毁的&#xff…

艾尔登法环地图不能使用鼠标移动或点击传送点原因和设置方法

今天玩艾尔登法环突发发现地图不能用鼠标点击传送点了。 找了半天发现设置地图选单的游标移动方式只有键盘了&#xff0c;改成键盘与鼠标就好啦。

【算法】——一键解决动态规划

前言 动态规划是一种高效解决​​重叠子问题​​和​​最优子结构​​问题的算法思想。它通过​​分治记忆化​​&#xff0c;将复杂问题分解为子问题&#xff0c;并存储中间结果&#xff0c;避免重复计算&#xff0c;从而大幅提升效率。 ​​为什么重要&#xff1f;​ ​​优化…

uniApp开发微信小程序-连接蓝牙连接打印机上岸!

历经波折三次成功上岸&#xff01; 三次经历简单絮叨一下&#xff1a;使用uniAppvue开发的微信小程序&#xff0c;使用蓝牙连接打印机&#xff0c;蓝牙所有的接口都是插件中封装的&#xff0c;用的插件市场中的这个&#xff1a; dothan-lpapi-ble &#xff1b;所以&#xff0c…

软件系统安全设计方案,信息化安全建设方案(Word原件)

1.1 总体设计 1.1.1 设计原则 1.2 物理层安全 1.2.1 机房建设安全 1.2.2 电气安全特性 1.2.3 设备安全 1.2.4 介质安全措施 1.3 网络层安全 1.3.1 网络结构安全 1.3.2 划分子网络 1.3.3 异常流量管理 1.3.4 网络安全审计 1.3.5 网络访问控制 1.3.6 完…

wsl2+ubuntu22.04安装blenderproc教程

本章教程,介绍如何在windows操作系统上通过wsl2+Ubuntu22.04上安装blenderproc。 一、pipi安装方式 推荐使用minconda3安装Python环境。 pip install Blenderproc二、源码安装 1、下载源码 git clone https://github.com/DLR-RM/BlenderProc2、安装依赖 cd BlenderProc &am…

Blender 转 STL 文件全攻略:从基础到进阶

在 3D 建模与打印领域&#xff0c;Blender 凭借其强大的功能和开源特性&#xff0c;深受创作者喜爱。而 STL 文件格式&#xff0c;作为 3D 打印行业的通用标准&#xff0c;能被绝大多数 3D 打印软件和设备所识别。因此&#xff0c;将 Blender 模型转换为 STL 文件&#xff0c;是…

Ansys Electronics 变压器 ACT

你好&#xff0c; 在本博客中&#xff0c;我将讨论如何使用 Ansys 电子变压器 ACT 自动快速地设计电力电子电感器或变压器。我将逐步介绍设计和创建电力电子变压器示例的步骤&#xff0c;该变压器为同心组件&#xff0c;双绕组&#xff0c;采用正弦电压激励&#xff0c;并应用…

nacos配置达梦数据库驱动源代码步骤

1.在父工程pom.xml添加依赖&#xff1a; <dependency><groupId>com.dameng</groupId><artifactId>DmJdbcDriver18</artifactId><version>8.1.1.193</version> </dependency> 2.在nacos-config模块pom.xml添加依赖&#xff1…

4.9-4.10学习总结 Stream流练习+方法引用+异常

Stream流练习&#xff1a; 1.打印数组内的偶数。 import java.util.*; import java.util.function.BiConsumer; public class test {public static void main(String[] args) {ArrayList<Integer> listnew ArrayList<>();Collections.addAll(list,1,2,3,4,5,6,7,…

FPGA系统开发板调试过程不同芯片的移植步骤介绍

目录 1.我目前使用的开发板 2.不同开发板的移植 步骤一&#xff1a;芯片型号设置 步骤二&#xff1a;约束修改 步骤三、IP核更新 关于FPGA系统开发板调试过程中不同芯片的移植。我需要先理清楚FPGA开发中移植到不同芯片的一般流程。首先&#xff0c;移植通常涉及到更换FPG…

复现QGIS-MCP教程

由于Claude国内下载不了尝试使用Cursor 下载安装Cursor Cursor - The AI Code Editor 本示例安装的是0.46版本 UV安装 简介 安装 安装成功 配置环境变量 验证 下载代码 git clone gitgithub.com:jjsantos01/qgis_mcp.git QGIS插件安装 文件拷贝 您需要将 qgis_mcp_plu…

java笔记03

基本数据类型 数据值是存储在自己的空间中。 特点&#xff1a;赋值给其他变量&#xff0c;也是赋的真实的值。 引用数据类型 数据值是存储在其他空间中&#xff0c;自己空间中存储的是地址值。 特点&#xff1a;赋值给其他变量&#xff0c;赋的地址值。 综合练习 使用 ctrl…

【开发工具】快速自定义图标元素的颜色

如果你想要一个轻量级、简单易用 的小工具来快速自定义图标元素的颜色&#xff08;比如调整 SVG/PNG 图标的颜色&#xff0c;或者生成多色图标&#xff09;&#xff0c;可以试试以下工具&#xff1a; 1. 在线工具&#xff08;无需安装&#xff09; SVG/PNG 图标改色 - Recol…

【CompletableFuture】异步编程

CompletableFuture异步编程 CompletableFuture介绍与传统 Future 的对比使用方法1. 使用 supplyAsync&#xff08;有返回值&#xff09;使用 runAsync&#xff08;无返回值&#xff09;指定自定义线程池 处理异步结果1. thenApply&#xff1a;转换结果2.thenAccept&#xff1a;…

【TS学习】(23)理解类的双重角色

在 TypeScript 中&#xff0c;类&#xff08;class&#xff09;不仅是一个运行时的值&#xff08;即可以实例化对象的构造函数&#xff09;&#xff0c;同时也是一个类型声明。具体来说&#xff0c;类在 TypeScript 中既声明了值&#xff0c;也声明了类型&#xff0c;并且它的类…