算法分析与设计编程题 回溯法

news2025/5/25 23:53:42

装载问题

题目描述

在这里插入图片描述

解题代码

递归回溯

// goods[i]表示货物i的重量, c1,c2分别表示货船1和货船2的载重量
vector<vector<int>> optimalLoading(vector<int>& goods, int c1, int c2) {
	int n = goods.size(); // 货物数量
	int maxSum = 0; // 当前最大载货量
	// curSelection[i]表示货物i是否放入货船1中(true表示放入)
	vector<bool> curSelection(n, false);
	// optimSelection记录maxSum对应的货物存放方式
	vector<bool> optimSelection;
	
    // 递归搜索函数
	function<void(int, int)> dfs = [&](int sum, int idx) {
		if (idx == n) { // 搜索达到最大深度,得到一个解
			if (sum > maxSum) { // 更新最优解
				maxSum = sum;
				optimSelection = curSelection;
			}
			return;
		}
		// 货物idx能否放入货船1,若能,则向下搜索
		if (sum + goods[idx] <= c1) {
			curSelection[idx] = true;
			dfs(sum + goods[idx], idx + 1);
			curSelection[idx] = false;
		}
		// 不考虑将货物idx放入货船1
		dfs(sum, idx + 1);
	};

	dfs(0, 0); // 执行搜索,初始时sum和idx均为0

	// 判断在最优解情况下(将尽可能重的货物放入货船1后),余下的货物能否放入货船2
	int sum2 = 0;
	for (int i = 0; i < n; ++i) {
		if (!optimSelection[i]) {
			sum2 += goods[i];
		}
	}
	if (sum2 > c2) return {}; // 若不能,则此问题无解,返回空数组
	// 若能,则构造最优解
	vector<vector<int>> res(2);
	for (int i = 0; i < n; ++i) {
		if (optimSelection[i]) { // 选择放入货船1
			res[0].emplace_back(goods[i]);
		}
		else { // 选择放入货船2
			res[1].emplace_back(goods[i]);
		}
	}
	return res;
}

状态压缩

事实上,对于此类涉及选或不选的回溯算法,还可以将其写成迭代的形式。

由于递归回溯的本质可以看作是对一棵二叉树进行的搜索,每次选或者不选都将产生两个分支,那么所有情况的数量为 2n(n 为被搜索对象的数目,在本例中为货物的总数)。我们考虑用一个整数 mask 将每种情况表示出来,该整数称为掩码,关注它的 n 位二进制形式,其中 mask 的第 i 位二进制位就代表对应的货物 goods[i] 有没有被选择,通常用 1 代表被选择,0 代表不被选择。那么不难得知 mask 的范围为 0~2n-1 。

在得到了每一种情况下的掩码后,就需要对其进行解码了,可以遍历 0~n-1 的所有整数 i,并将其右移 i 位,将 goods[i] 的对应的二进制位移到了最低位,此时再将和 1 进行按位与运算就能得知此情况下货物 i 是否被选择。

两种算法都有 2n 中搜索状态,每种状态下需要 O(n) 时间来进行最优解的更新,因此两种算法的渐进时间复杂度都为 O(n * 2n).

vector<vector<int>> optimalLoading(vector<int>& goods, int c1, int c2) {
	int n = goods.size();
	vector<bool> curSelection(n, false);
	vector<bool> optimSelection;
	int maxSum = 0;
	for (int mask = 0; mask < (1 << n); ++mask) { // 遍历每种情况
		// 将sum和curSelection全部复位
        int sum = 0;
		curSelection.assign(n, false);
		for (int i = 0; i < n; ++i) {
			bool isChoosed = (mask >> i) & 1;
			if (!isChoosed) continue;
			if (sum + goods[i] <= c1) {
				sum += goods[i];
				curSelection[i] = true;
			}
		}
		if (sum > maxSum) {
			maxSum = sum;
			optimSelection = curSelection;
		}
	}
    
    // 构造最优解(与递归回溯部分完全相同)
	int sum2 = 0;
	for (int i = 0; i < n; ++i) {
		if (!optimSelection[i]) {
			sum2 += goods[i];
		}
	}
	if (sum2 > c2) return {};
	vector<vector<int>> res(2);
	for (int i = 0; i < n; ++i) {
		if (optimSelection[i]) {
			res[0].emplace_back(goods[i]);
		}
		else {
			res[1].emplace_back(goods[i]);
		}
	}
	return res;
}

批处理作业调度

题目描述

在这里插入图片描述

解题代码

int batchJobScheduling(vector<vector<int>>& jobs) {
	int n = jobs.size(); // 作业数量
	int res = INT32_MAX, curSum = 0; // 最优调度时间,当前调度时间
	int f1 = 0; // 机器1完成处理时间
	vector<int> f2(n + 1, 0); // 机器2完成处理时间
	vector<int> optimSche; // 最优调度方案
	vector<int> curSche; // 当前调度方案
	for (int i = 0; i < n; ++i) { // 初始调度方案为 0,1,2,...,n-1
		curSche.emplace_back(i);
	}

    // 递归搜索函数
	function<void(int)> dfs = [&](int idx) {
		if (idx == n) { // 搜索达到最大深度
            // 更新最优解
			optimSche = curSche;
			res = curSum;
			return;
		}
		for (int j = idx; j < n; ++j) { // 全排列搜索
			f1 += jobs[curSche[j]][0];
			f2[idx + 1] = ((f2[idx] > f1) ? f2[idx] : f1) + jobs[curSche[j]][1];
			curSum += f2[idx + 1];
			if (curSum < res) { // 剪枝(若当前累计和已经大于等于最优解,则不继续向下搜索)
				swap(curSche[idx], curSche[j]);
				dfs(idx + 1);
				swap(curSche[idx], curSche[j]);
			}
            // 回溯
			f1 -= jobs[curSche[j]][0];
			curSum -= f2[idx + 1];
		}
	};

	dfs(0); // 递归搜索
    // 打印最优调度方案
	for (int i = 0; i < n; ++i) {
		cout << optimSche[i];
		if (i < n - 1) cout << "->";
	}
	return res;
}

符号三角形问题

题目描述

在这里插入图片描述

解题代码

int signedTriangle(int n) {
	int num = n * (n + 1) / 2; // 三角形符号总数
	if (num % 2 == 1) return 0; // 总数为奇数,不可能相等
	vector<bool> triangles(num, false); // false代表'+',true代表'-'
	int res = 0; // 合法图形个数
	vector<vector<bool>> fullShape; // 所有合法图形
	
    // 递归搜索函数
	function<void(int)> dfs = [&](int idx) {
		if (idx == n) { // 到达最大搜索深度,判断是否可行
			int pCnt = num / 2, sCnt = num / 2; // 剩余'+','-'的符号个数
			vector<bool> newShape; // 当前图形
			queue<bool> q, nq; // 轮换队列
			for (int i = 0; i < n; ++i) { // 将第一行符号加入队列
				q.emplace(triangles[i]);
				newShape.emplace_back(triangles[i]);
				--(triangles[i] ? sCnt : pCnt);
			}
			while (q.size() > 1) {
				while (q.size() > 1) {
					bool ft = q.front();
					q.pop();
					bool nt = ft ^ q.front(); // 队列前两个符号异或得到下面的符号
					nq.emplace(nt);
					--(nt ? sCnt : pCnt); 
					if (sCnt * pCnt < 0) return; // 其中一个符号个数超过一半
					newShape.emplace_back(nt);
				}
				q = move(nq); // 队列轮换(利用右值引用进行资源所有权的交换)
			}
            // 该结果合法
			++res;
			fullShape.emplace_back(newShape);
			return;
		}
		triangles[idx] = true;
		dfs(idx + 1);
		triangles[idx] = false;
		dfs(idx + 1);
	};
	
	dfs(0); // 递归搜索
    // 打印所有合法图形
	for (const auto& shape : fullShape) {
		for (int col = n; col >= 1; --col) {
			int di = (n - col) * (n + col + 1) / 2;
			for (int i = 0; i < col; ++i) {
				cout << shape[i + di];
			}
			cout << endl;
		}
		cout << "\n" << endl;
	}
	return res;
}

N皇后

题目描述

在这里插入图片描述

解题代码

vector<vector<string>> solveNQueens(int n) {
    vector<vector<string>> res; // 存放所有解
    vector<string> chessBoard(n, string(n, '.')); // 当前棋盘状态

    // 检查该位置(r,c)是否能够放置棋子
    auto check = [&](int r, int c) -> bool {
        for (int i = 0; i < r; ++i) {
            // 从上往下依次检查棋盘第c列是否已放置棋子
            if (chessBoard[i][c] == 'Q') {
                return false;
            }
            // 从下往上依次检查左斜上方是否已放置棋子
            int li = r - i - 1, lj = c - i - 1;
            if (li >= 0 && lj >= 0 && chessBoard[li][lj] == 'Q') {
                return false;
            }
            // 从下往上依次检查右斜上方是否已放置棋子
            int ri = r - i - 1, rj = c + i + 1;
            if (ri >= 0 && rj < n && chessBoard[ri][rj] == 'Q') {
                return false;
            }
        }
        return true;
    };

    // 递归搜索函数
    function<void(int)> dfs = [&](int idx) {
        if (idx == n) { // 到达最大深度,得到一个合法解
            res.emplace_back(chessBoard);
            return;
        }
        for (int j = 0; j < n; ++j) {
            if (!check(idx, j)) continue; // 当前位置不可放置
            chessBoard[idx][j] = 'Q'; // 放置棋子
            dfs(idx + 1);
            chessBoard[idx][j] = '.'; // 回溯
        }
    };

    dfs(0);
    return res;
}

最大团问题

题目描述

在这里插入图片描述

解题代码

// 图的邻接矩阵形式
struct MGraph {
	vector<char> vertices; // 顶点数组(元素为字符类型)
    // 邻接矩阵,edges[u][v] == INT32_MAX ? 无边 : 存在权值为edges[u][v]的边
	vector<vector<int>> edges; 
};

vector<vector<char>> largestGroup(MGraph& G) {
	int n = G.vertices.size(); // 顶点个数
     // 所有的最大团(每个最大团为一个char类型数组,其中元素为最大团顶点)
	vector<vector<char>> res;
	vector<bool> curGroup(n, false); // 当前团
	int maxSize = 0; // 团的最大顶点数

    // 递归搜索函数,idx为搜索深度,curSize为当前搜索状态下团的顶点个数
	function<void(int, int)> dfs = [&](int idx, int curSize) {
		if (idx == n) { // 到达最大搜索深度
            // 构造最大团
			vector<char> group;
			for (int i = 0; i < n; ++i) {
				if (curGroup[i]) {
					group.emplace_back(G.vertices[i]);
				}
			}
            // 更新最优解
			if (curSize > maxSize) {
				res.clear();
				maxSize = curSize;
			}
			res.emplace_back(group);
			return;
		}
		bool flag = true; // 判断当前结点idx是否能够与当前团的每个结点相连
		for (int j = 0; j < idx; ++j) {
			if (curGroup[j] && G.edges[idx][j] == INT32_MAX) {
				flag = false;
				break;
			}
		}
		if (flag) { // 如果相连,则构成一个更大的团,继续向下搜索
			curGroup[idx] = true; // 加入团
			dfs(idx + 1, curSize + 1);
			curGroup[idx] = false; // 回溯
		}
        // 剪枝,若满足curSize + n - idx <= maxSize
        // 则不可能得到比当前最大团更大的团,无需继续搜索
		if (curSize + n - idx > maxSize) {
			dfs(idx + 1, curSize);
		}
	};

	dfs(0, 0);
	return res;
}

图的m着色问题

题目描述

在这里插入图片描述

解题代码

// 图的邻接矩阵形式
struct MGraph {
	vector<char> vertices; // 顶点数组(元素为字符类型)
    // 邻接矩阵,edges[u][v] == INT32_MAX ? 无边 : 存在权值为edges[u][v]的边
	vector<vector<int>> edges; 
};

vector<vector<int>> mColoring(MGraph& G, int m) {
	int n = G.vertices.size(); // 图的顶点个数
	vector<vector<int>> res; // 所有着色方案,若无合法着色方案,则为空
	vector<int> coloring(n, -1); // 当前着色方案

    // 检查所有与顶点idx相连的顶点j是否与顶点idx颜色相同,若相同,则此着色方案不合法
	auto check = [&](int idx) -> bool {
		for (int j = 0; j < n; ++j) {
			if (G.edges[idx][j] != INT32_MAX && coloring[idx] == coloring[j]) {
				return false;
			}
		}
		return true;
	};

    // 递归搜索函数
	function<void(int)> dfs = [&](int idx) {
		if (idx == n) { // 到达最大搜索深度,将该着色方案加入解集中
			res.emplace_back(coloring);
			return;
		}
       	// 遍历所有颜色,尝试为顶点idx进行着色
		for (int j = 0; j < m; ++j) {
			coloring[idx] = j; // 着色
			if (check(idx)) { // 此着色合法,继续向下搜索
				dfs(idx + 1);
			}
			coloring[idx] = -1; // 回溯
		}
	};

	dfs(0);
	return res;
}

圆排列问题

题目描述

在这里插入图片描述

解题代码

double circlePermutation(vector<double>& radius) {
	int n = radius.size(); // 圆的个数
	double res = INT32_MAX; // 最小长度
	vector<double> optimalPerm; // 最小长度对应的排列方式
	vector<double> curX(n); // curX[i]表示当前排列下圆i的圆心横坐标
	
    // 计算当前排列下圆idx的圆心横坐标
	auto calCenter = [&](int idx) -> double {
		double xMax = 0.0;
		for (int j = 0; j < idx; ++j) {
			double x = curX[j] + 2.0 * sqrt(radius[idx] * radius[j]);
			xMax = max(xMax, x);
		}
		return xMax;
	};

    // 计算当前排列下的总长度
	auto calLen = [&]() -> double {
		double low = 0.0, high = 0.0;
		for (int i = 0; i < n; ++i) {
			low = min(low, curX[i] - radius[i]);
			high = max(low, curX[i] + radius[i]);
		}
		return high - low;
	};

    // 递归搜索函数
	function<void(int)> dfs = [&](int idx) {
		if (idx == n) { // 到达最大搜索深度
			double len = calLen();
			if (len < res) { // 更新最优解
				res = len;
				optimalPerm = radius;
			}
		}
		for (int j = idx; j < n; ++j) { // 全排列
			swap(radius[idx], radius[j]);
			double centerX = calCenter(idx);
			if (centerX + radius[idx] + radius[0] < res) { // 剪枝
				curX[idx] = centerX;
				dfs(idx + 1);
			}
			swap(radius[idx], radius[j]);
		}
	};

	dfs(0);
    // 打印最优解对应的圆排列
	for (int i = 0; i < n; ++i) {
		cout << optimalPerm[i] << " ";
	}
	return res;
}

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

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

相关文章

穿山甲报错 splashAdLoadFail data analysis error

使用swift接入穿山甲&#xff0c;未接入GroMore&#xff0c;这个时候如果代码位配置错误会导致如下错误&#xff1a; splashAdLoadFail(_:error:) Optional(“Error Domaincom.buadsdk Code98764 “data analysis error” UserInfo{NSLocalizedDescriptiondata analysis error,…

基于YOLOv8模型的海洋生物目标检测系统(PyTorch+Pyside6+YOLOv8模型)

摘要&#xff1a;基于YOLOv8模型的海洋生物目标检测系统可用于日常生活中检测与定位海洋生物目标&#xff0c;利用深度学习算法可实现图片、视频、摄像头等方式的目标检测&#xff0c;另外本系统还支持图片、视频等格式的结果可视化与结果导出。本系统采用YOLOv8目标检测算法训…

贝类包纳米虫病诊断方法

声明 本文是学习GB-T 42821-2023 贝类包纳米虫病诊断方法. 而整理的学习笔记,分享出来希望更多人受益,如果存在侵权请及时联系我们 242 g 57.1 mL 100 mL 1000 mL A.9 1 电泳缓冲液 50电泳缓冲液 加水定容至 室温贮存。 A.10 苏木精染液的配制方法 苏木精 无水乙醇 …

第36章_瑞萨MCU零基础入门系列教程之步进电机控制实验

本教程基于韦东山百问网出的 DShanMCU-RA6M5开发板 进行编写&#xff0c;需要的同学可以在这里获取&#xff1a; https://item.taobao.com/item.htm?id728461040949 配套资料获取&#xff1a;https://renesas-docs.100ask.net 瑞萨MCU零基础入门系列教程汇总&#xff1a; ht…

基于Python的UG二次开发入门

文章目录 基于Python的UG二次开发入门1 二次开发环境搭建1.1 安装UG1.2 安装Pycharm1.3 环境配置1.4 测试 2 NX Open介绍2.1 基础架构2.1.1 Sessions and Parts2.1.2 Objects and Tags2.1.3 Factory Objects&#xff08;工厂对象&#xff09;2.1.4 Builder Objects&#xff08;…

科兴未来 | 第十届中国(泰州)国际大健康产业高层次人才创新创业大赛公告

为加快推进青年和人才友好型城市建设&#xff0c;吸引和集聚更多海内外生物医药高层次人才来泰创新创业&#xff0c;推动大健康产业高质量发展&#xff0c;全力建设“健康名城、幸福泰州”&#xff0c;现举办第十届中国&#xff08;泰州&#xff09;国际大健康产业高层次人才创…

15W sip网络有源音箱,可外接15W无源副音箱

SV-7042VP 15W sip网络有源音箱,可外接15W无源副音箱 一、描述 SV-7042VP是深圳锐科达电子有限公司的一款壁挂式SIP网络有源音箱&#xff0c;具有10/100M以太网接口&#xff0c;可将网络音源通过自带的功放和喇叭输出播放&#xff0c;可达到功率15W。同时它可以外接一个15W的…

STM32WB55开发(4)----配置串口打印Debug调试信息

STM32WB55开发----4.配置串口打印Debug调试信息 概述硬件准备视频教学样品申请选择芯片型号配置时钟源配置时钟树RTC时钟配置查看开启STM32_WPAN条件配置HSEM配置IPCC配置RTC启动RF开启蓝牙开启串口调试配置蓝牙参数设置工程信息工程文件设置Keil工程配置代码配置结果演示 概述…

000_差分信号

1.什么是差分信号 差分信号又叫做差模信号&#xff0c;使用差分信号传输时&#xff0c;需要2根信号线&#xff0c;这2根信号线的振幅相等&#xff0c;相位相反&#xff0c;通过2根信号线的电压差值来表示逻辑0和逻辑1。 差分信号表示逻辑值如下图&#xff1a; 2.差分信号的特…

合并单元格中自动填充数字序列的方法详解

我们如何在Excel中将序列号填充到不同大小的合并单元格列表中?我们首先想到的是拖动“自动填充”手柄来填充合并的单元格,但在这种情况下,我们将收到以下警告消息,并且无法填充合并的单元。 有没有一种方法可以在不必手动键入数字的情况下对合并的单元格进行编号? 例如,…

深兰科技入围“虎嗅·大鲸榜2023工业AI高成长科技公司TOP30”

9月7日&#xff0c;虎嗅智库主办的“虎嗅2023工业AI大会”&#xff0c;在苏州工业园顺利召开&#xff0c;众多优秀工业和AI企业齐聚一堂&#xff0c;共同探讨当下人工智能在工业制造各场景、环节中所发挥的重要作用以及未来发展等重要问题。 在本次大会上&#xff0c;虎嗅正式发…

PIMPL技巧

PIMPL&#xff08;Pointer to IMPLementation&#xff09;是一种设计模式&#xff0c;也被称为“编译器实现”或“Opaque Pointer”模式。它是一种用于隐藏类的内部实现细节的C编程技巧。PIMPL的核心思想是将类的实现细节封装在一个独立的私有类中&#xff0c;并在公共接口类中…

左孩子右兄弟(2023寒假每日一题 18)

对于一棵多叉树&#xff0c;我们可以通过 “左孩子右兄弟” 表示法&#xff0c;将其转化成一棵二叉树。 如果我们认为每个结点的子结点是无序的&#xff0c;那么得到的二叉树可能不唯一。 换句话说&#xff0c;每个结点可以选任意子结点作为左孩子&#xff0c;并按任意顺序连…

【antd】使用antd的table组件onChange事件中,无法正确获取到父组件的最新state问题

先说结论 是闭包的原因导致我们的state数据在useEffect注入依赖后&#xff0c;打印的是最新值&#xff0c;而在onTableChange事件中打印获取的是闭包时的缓存值。 解决办法 引入useRef去保存state&#xff0c;这样就能确保每次拿到的引用都是新的唯一的最新值。 代码示例&a…

C++之调试内存访问错误(二百一十一)

简介&#xff1a; CSDN博客专家&#xff0c;专注Android/Linux系统&#xff0c;分享多mic语音方案、音视频、编解码等技术&#xff0c;与大家一起成长&#xff01; 优质专栏&#xff1a;Audio工程师进阶系列【原创干货持续更新中……】&#x1f680; 人生格言&#xff1a; 人生…

PythonUI自动化测试 —— 浏览器启动参数设置

网上的文章对小白不友好呀&#xff0c;都是给你一堆参数&#xff0c;都不教你怎么使用&#xff0c;直接跳过了最重要的部分&#xff0c;写下该文章希望对后续人有指导性作用 什么参数都不设置时的启动方式 import time from selenium import webdriver# 创建浏览器驱动参数对象…

NSSCTF之Misc篇刷题记录(16)

NSSCTF之Misc篇刷题记录&#xff08;16&#xff09; [黑盾杯 2020]encrypt[UTCTF 2020]Spectre[UTCTF 2020]Observe closely NSSCTF平台&#xff1a;https://www.nssctf.cn/ PS&#xff1a;所有FLAG改为NSSCTF [黑盾杯 2020]encrypt UTAxSlUwTkRWRVo3Um1GclpWOWxibU55ZVhCMGFX…

选择离子风棒需要注意什么?

离子风棒是一种固定式静电消除器&#xff0c;具有铜质外壳、铝制外壳、不锈钢外壳、坚固美观、风力强劲、经久耐用、耐酸碱、耐腐蚀、除静电除尘快的特点&#xff0c;适用于自动除静电除灰尘装置。 作用原理&#xff1a;离子风棒可产生大量的带正负电荷的气团&#xff0c;可以…

UG\NX二次开发 获取工作部件的完整名称、文件名称、文件夹路径 UF_ASSEM_ask_component_data

文章作者:里海 来源网站:王牌飞行员_里海_里海NX二次开发3000例,里海BlockUI专栏,C\C++-CSDN博客 简介: UG\NX二次开发 获取工作部件的完整名称、文件名称、文件夹路径 UF_ASSEM_ask_component_data 效果: 代码: //获取工作部件的完整名称、文件名称、文件夹路径…

算法分析与设计编程题 动态规划

矩阵连乘 题目描述 解题代码 void printOptimalParens(vector<vector<int>>& partition, int i, int j) {if (i j) cout << "A" << i; // 单个矩阵&#xff0c;无需划分else {cout << "(";printOptimalParens(partit…