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

2023-11-17

装载问题

题目描述

在这里插入图片描述

解题代码

递归回溯

// 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;
}
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

算法分析与设计编程题 回溯法 的相关文章

  • ReferenceError: XXX is not defined 错误及解决办法

    ReferenceError XXX is not defined 错误及解决办法 我这里报错是忘记了引入此方法所在的js文件 解决办法 引入所需的js文件 此错误 另外一种情况就是 jQuery引入先后顺序不对 要先引入jQuery文件
  • emmc学习

    1 介绍 1 1 简介 emmc embedded multi media card eMMC的一个明显优势是在封装中集成了一个控制器 它提供标准接口并管理闪存 使得手机厂商就能专注于产品开发的其它部分 并缩短向市场推出产品的时间 这些特点

随机推荐

  • pikachu之文件下载和文件上传

    目录 一 文件下载 1 复制这个下载文件地址 2 尝试去下载这个down nba php 3 目录扫描工具 二 文件上传 1 checkclient 1 利用burp suite 2 关闭js 3 修改页面源代码 2 mime类型验证 3
  • IO多路复用机制详解

    高性能IO模型浅析 服务器端编程经常需要构造高性能的IO模型 常见的IO模型有四种 1 同步阻塞IO Blocking IO 即传统的IO模型 2 同步非阻塞IO Non blocking IO 默认创建的socket都是阻塞的 非阻塞IO
  • 机器学习模型融合

    模型融合方式 均值法Averaging 适用于回归类算法 将每个评估器的输出做平均 类似于Bagging中回归的做法 投票法Voting 适用于分类算法 将每个评估器的输出进行投票 类似于Bagging中分类的做法 堆叠法Stacking
  • heapdump文件找相关的键值

    前言 一次做项目遇到heapdump文件 也是第一次碰到所以记录一下过程 步骤 下载heapdump文件是一个 hprof gz后缀的文件 使用MemoryAnalyzer这个工具就可以进行对其中的键值进行分析 工具的安装 下载好Memor
  • C++:主要的关联式容器类型:set

    目录 1 关联式容器 2 键值对 3 树形结构的关联式容器 4 set 5 set的特点 6 set的使用 常用接口的使用 1 insert 2 find 3 erase 4 operator 7 multiset 1 关联式容器 与vec
  • 解决pyqt中mainwindow界面最大化按钮是灰色(不能最大化)的问题

    解决方法 将maximumSize的值设置为16777215x16777215即可使窗口打开时最大化按钮可用 MAIN SIZE MAX QSize 16777215 16777215 self setMaximumSize MAIN SI
  • 系统服务器迁移,应用系统和服务器迁移云

    应用系统和服务器迁移云 内容精选 换一换 公有云通常指第三方供应商为用户提供的能够通过Internet使用的云端基础设施和服务 其核心属性是共享资源服务 华为云是公有云品牌 在SAP系统迁移的过程中 您可以单独使用这些华为云云服务 也可以组
  • Python方便又强大的日志记录器——loguru

    Python方便又强大的日志记录器 loguru Loguru是一个旨在使Python日志记录变得愉快的库 该库通过添加一系列有用的功能来解决标准记录器的警告 从而减少Python日志记录的痛苦 日志记录对每个应用程序都是基本的 它简化了调
  • rosserial_arduino

    rosserial arduino 一级目录 二级目录 三级目录 wiki tutorials 1 安装arduino IDE 2 Installing the Software 2 1 Installing on the ROS work
  • linux charg修改目录,Thinkpad在GUN/linux(ubuntu)下修改电池充电阈值

    详见 http www thinkwiki org wiki Tp smapi 安装tp smapi aptitude install tp smapi dkms modprobe tp smapi 更改充电阈值 设置开始充电阈值 如从 6
  • 02

    1 克隆镜像 前提 centos 7 选择 管理 gt 克隆 选择 下一步 选择 虚拟机中的当前状态 选择 创建完整克隆 设置 虚拟机名称 和 位置 克隆完成界面 2 导入镜像 打开虚拟机 选择对应的克隆文件 导入后的结果 3 网络设置 3
  • 铨顺宏RFID:根据UWB技术性的矿山开采人员精准定位系统

    计划方案环境及总体目标 伴随着智能技术的持续发展趋势 矿山开采生产模式不断创新 翠绿色 绿色生态 智能化慢慢变成煤业的追求完美 煤业智能化系统 智能化针对矿山开采而言十分关键 不但可以产生生产量的提升 还能够协助减少资金投入和产品成本 提高
  • 科技云报道:震惊!4K、8K画质背后,竟然少不了AI的助力

    科技云报道原创 对于视频的画质 我现在最低只能够接受720P 最好是1080p 早五年前 身边就已经有人提出了这样的要求 随着科技的进步 我们进入了一个视频内容快速增长的时代 从社交媒体到在线教育 从直播购物到虚拟会议 视频正逐渐成为主流的
  • Jiu Yuan Wants to Eat【2018焦作网络赛】【树链剖分】

    题目链接 树链剖分学习笔记 可以看这里 这道题还真挺好的 以前不会做 现在想了发现 学过树链剖分之后 剩下的部分就是处理去反那块比较的不容易些了 但是想了一下午 现在还是给我敲出来了 我们主要难处理的就是关于求反 那么怎么处理求反 一开始读
  • antd-select下拉框如何同时获取所选值ID和名字属性

    其中传过去的e为 从antd官网可以看到select组件的onchange属性传参 第一个参数为value 第二个参数可为所选中的那一项的所有信息 其中的props属性内有一个children属性 即存储了所选项的名称
  • JWT快速入门及所需依赖

    目录 1 JWT 1 1什么是JWT 1 2JWT的构成 jwt的头部 payload signature 1 3JWT快速入门案例 2Jwt认证 微服务 2 1微服务下统一权限认证 2 2应用认证 3 无状态的JWT令牌如何实现续签功能
  • 蓝桥杯2022年第十三届省赛真题-最优清零方案--java语言

    题目链接 https www dotcpp com oj problem2689 html 一开始没仔细看题目 以为是自然连续数 搞得我还发了篇提问 怪尴尬的 https ask csdn net questions 7913935 spm
  • MongoDB、Elasticsearch分组统计性能比较

    环境参数 CentOS 7 6 虚拟机 4核 8GB Elasticsearch 5 6 16 MongoDB 5 0 9 数据结构 userId rkyao searchId 6e1c409ed7484a6a8a795e750bef9e2
  • 空间向量及值插值相关函数

    1 两个向量之间插值 float3 slerpBetweenVector float3 from float3 to float delta quatf model start quatf fromDirectedRotation norm
  • 算法分析与设计编程题 回溯法

    装载问题 题目描述 解题代码 递归回溯 goods i 表示货物i的重量 c1 c2分别表示货船1和货船2的载重量 vector