数据结构与算法_五大算法之--回溯算法

news2024/9/25 7:21:37

1 回溯算法

回溯算法具有通用性,但是算法的效率不高,通常可以通过剪枝等操作提高算法的效率。
算法思想:
在包含问题的所有解空间树中,按照深度优先搜索的策略,从根节点出发,深度搜索解空间树。当搜索到某一个节点时候,先判断该节点是否包含问题的解,如果包含就从该节点触发继续深度搜索下去,否则回溯;
解空间:
解空间就是所有解的可能取值构成的空间,一个解往往包含了得到这个解的每一步,往往就是对应解空间树中一条从根节点到叶子节点的路径。子集树和排列树都是一种解空间,它们不是真实存在的数据结构,也就是说并不是真的有这样一颗树,只是抽象出的解空间树。

1.1 解空间—子集树

子集树通常用于求某个问题的子集,涉及到子集的问题,看是否可以用子集树方式。

void func(int arr[], int i, int length,int x[])
{
	if (i == length)
	{
		for (int j = 0; j < length; j++)
		{
			if (x[j] == 1)
			{
				cout << arr[j] << " ";
			}
		}
		cout << endl;
	}
	else
	{
		x[i] = 1;  // 选择i节点
		func(arr, i + 1, length,x); // 遍历 i 的左孩子
		
		x[i] = 0; // 不选择i节点
		func(arr, i + 1, length,x); // 遍历i的右孩子
		//func(arr, i + 1, length); // 三叉树
	}
}

int main()
{
	int arr[] = { 1, 2, 3 };
	int length = sizeof(arr) / sizeof(arr[0]);
	int x[3] = { 0 };
	func(arr, 0, length,x);
	system("pause");
	return 0;
}

对代码的理解:

子集树中的两个循环,在逻辑上生成二叉树。
上面在递归过程中,一个节点(栈帧),向左递归标记为1,向右标记为0,打印时候,按照条件打印。
上边对递归左右走做一个标记,出来的就是子集树。算法时间复杂度,O(2^n)。

子集树总结

1 , 新开辟的栈帧中,从第一句代码开始执行;即使,第二句代码开辟的栈帧,仍然从新开辟的栈帧的第一句开始执行;
2 , 回溯就是结束,回溯到上一个节点后,如果后边有语句,继续执行后边的语句;
3 , 每一层栈帧中i节点相同,也就是层数相同。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

1.2 解空间— 排列树

排列树就是获取某个集合的全排列,比如求1 2 3这组数的全排列;
假如求1 2 3 全排列

void swap(int arr[], int i, int j)
{
	int temp = arr[i];
	arr[i] = arr[j];
	arr[j] = temp;
}

void func(int arr[], int i, int length)
{
	if (i == length)
	{
		for (int j = 0; j < length; j++)
		{
			cout << arr[j] << " ";
		}
		cout << endl;
	}
	else
	{
		for (int k = i; k < length; k++)
		{
			swap(arr, i, k);
			func(arr, i + 1, length);
			swap(arr, i, k);
		}
	}
}

int main()
{
	int arr[] = { 1,2,3 };
	int length = sizeof(arr) / sizeof(arr[0]);

	func(arr, 0, length);

	system("pause");
	return -1;
}

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

1.3 整数选择问题

整数选择:给定一组整数,从里面挑选出一组整数,让选择的整数的和 与 剩下的整数的和的差最小;

int min = 10000;
int result = 0;
int Xsum = 0;   //  记录选择的元素之和
int Ysum = 0;	//  记录没有选择的元素之和
void func(int arr[],int i,int length,int x[])
{
	if (i == length)
	{
		for (int j = 0; j < length; j++)
		{
			if (x[j] == 1)
			{
				
			}
			else
			{
				Ysum += arr[j];	// 记录未选择的元素之和
			}
		}
		result = abs(Xsum - Ysum);
		Xsum = 0;
		Ysum = 0;
		if (result <= min)
		{
			min = result;
		}
		cout << endl;
	}
	else
	{
		x[i] = 1;// 1 从当前节点向左走
		Xsum += arr[i];
		func(arr, i + 1, length,x);

		Ysum += arr[i];
		x[i] = 0;		// 2 回溯到父节点,然后从父节点向右走
		func(arr, i + 1, length,x);
	}
}

int main()
{
	int arr[] = { 12,6,7,11,16,3,8 };
	int length = sizeof(arr) / sizeof(arr[0]);
	int x[7] = { 0 };
	func(arr, 0, length,x);
	cout << "选择和剩下的整数之和为:" << min << endl;
	system("pause");
	return 0;
}

1.4 2N整数选择问题-加入剪枝操作

给2n个整数,从里面挑选出n个整数,让选择的整数的和 与 剩下的整数的和的差值最小;

int arr[] = { 12,6,7,11,16,3,5,23};
int length = sizeof(arr) / sizeof(arr[0]);
vector<int> bestM;		// 记录选择的最小值
vector<int> M;			// 记录子集中选择的元素 
unsigned int minval = 0xffffffff;
int sumR = 0;	// 记录数组中未被选择的元素之和
int sumS = 0;	// 记录选中的数组元素总和
int cnt = 0;  // 记录遍历的子集的个数
int left1 = length;
// 未加入剪枝操作
void func1(int i)
{
	if (i == length)
	{
		cnt++;
		if (M.size() != length / 2)
		{
			return;
		}

		int result = abs(sumS - sumR);
		if (result < minval)
		{
			minval = result;
			bestM = M;
		}
	}
	else
	{
		M.push_back(arr[i]);   // 记录选择的元素
		sumR -= arr[i];
		sumS += arr[i];
		func1(i + 1);
		// a
		cout << endl;
		sumS -= arr[i];
		sumR += arr[i];
		M.pop_back();

		func1(i + 1);
	}
}
// 加入剪枝操作 
void func(int i)
{
	if (i == length)
	{	
		cnt++;
		if (M.size() != length / 2)
		{
			return;
		}
	
		int result = abs(sumS - sumR);
		if (result < minval)
		{
			minval = result;
			bestM = M;
		}
	}
	else
	{
		left1--;
		if (M.size() < length / 2)   // 减左树枝,还未选择够n个整数
		{
			// 下面三行,递归做的事情
			M.push_back(arr[i]);   // 记录选择的元素
			sumR -= arr[i];
			sumS += arr[i];

			func(i + 1);
			// 回溯
			sumS -= arr[i];
			sumR += arr[i];
			M.pop_back();
		}
		// 右树枝可以选吗?已选择的数字的个数 + 未来能选择的所有数组个数(i+1,i+2,...i+n) >= n个元素
		//				   已选择的数字的个数 + 未来能选择的所有数组个数(i+1,i+2,...i+n) <= n个元素  则不执行 
		if (M.size() + left1 >= length / 2)
		{
			func(i + 1);
		}
		left1++;
	}
}

int main()
{
	// 计算出数组总和
	for (int v : arr)
	{
		sumR += v;
	}
	func(0);
	
	cout << "最小值" << minval << endl;
	for (int v : bestM)
	{
		cout << v << " ";
	}
	cout << endl;
	cout << "cnt:  " << cnt << endl;
	system("pause");
	return 0;
}

1.5 挑选一组数字,让他们的和等于指定的值

挑选数字:有一组整数,请挑选出一组数字,让他们的和等于指定的值,存在解打印,不存在打印

int arr[] = { 12,6,7,11,16,3,5,23 };
const int number = 34;
int length = sizeof(arr) / sizeof(arr[0]);
vector<int> x;  // 记录选择的数字
int sum = 0;	// 记录选择的数字之和
int r = 0;		// 记录未处理的数字的和

void func(int i)
{
	if (i == length)
	{
		if (sum != number)
		{
			return;
		}
		for (int v : x)
		{
			cout << v << " ";
		}
		cout << endl;

	}
	else
	{
		r -= arr[i];
		// 加入剪枝操作,减左枝:  如果已选择的数字的和 + 加上即将选择的的数字的和 < number 
		if (sum + arr[i] <= number)
		{
			sum += arr[i];		 // 记录所选数字之和
			x.push_back(arr[i]);
			func(i + 1);

			sum -= arr[i];		 // 减去右子树中的元素
			x.pop_back();
		}
		if (sum + r >= number)
		{
			func(i + 1);
		}

		r += arr[i];
	}
		
}

int main()
{
	for (int v : arr)
	{
		r += v;
	}

	func(0);
	// 计算出数组总和
	system("pause");
	return 0;
}

在这里插入图片描述

1.6 穷举法 (挑选数字)

穷举法和子集树的关系:穷举法所有的可能是子集树的子集;子集树能解决的问题,穷举法可能解决不了;
对代码的理解:
for的第一层 1 2 3 4 ;以1为确定的值第二层 2 3 4 ,以2为可能值的第2层,3,4;等等。
在这里插入图片描述

int arr[] = { 12,6,7,11,16,5,5,23 };
int number = 34;
int length = sizeof(arr) / sizeof(arr[0]);
vector<int> vec;  // 存放选择的数字

void func(int i, int number)
{
	if (number == 0)
	{
		for (int v : vec)
		{
			cout << v << " ";
		}
		cout << endl;
	}
	else
	{
		for (int k = i; k < length; k++)
		{
			if (number >= arr[k])// number大于下一个元素,比如number=8,下一个元素为13,不满足;如果下一个元素为3,number>=3,满足
			{
				vec.push_back(arr[k]);

				func(k + 1, number - arr[k]); // Andy: k+1这种方式表示不允许重复选择元素,只能遍历当前元素的孩子节点
				//func(k, number - arr[k]);// Andy: k这种方式表示允许重复选择元素,只能遍历当前元素的孩子节点

				vec.pop_back();
			}
		}
	}
}

int main()
{
	func(0,number);
	// 计算出数组总和
	system("pause");
	return 0;
}

1.7 0-1背包

有一组物理,其重量分别是w1 w2 w3,,其价值分别是v1 v2 ,vn,现在有一个背包,其容量是C,问怎么把物品装入背包,能够使背包的价值最大化。
解法:用子集树求解

int w[] = { 12,5,8,9,6 };  // 物品的重量
int v[] = { 9,2,4,7,8 };   // 物品的价值,物品和价值是一对一关系 
int C = 25;

const int length = sizeof(w) / sizeof(w[0]);

int cw = 0; // 已经选择的物品的重量;
int cv = 0; // 已经选择的物品的价值 

vector<int> x; // 记录已经选择的物品 
vector<int> bestx; // 记录最优解 
int bestv = 0;
int r = 0; // 记录剩余元素的总价值
int cnt = 0;

void func(int i)
{
	if (i == length)
	{
		cnt++;
		if (C == cw)
		{
			if (bestv < cv)
			{
				bestv = cv;
				bestx = x;
			}
		}

	}
	else
	{
		r -= v[i];
		// 开始加入剪枝操作,减左枝
		if (cw + w[i] <= C)  // 已选择物品重量 + 即将选择的物品重量,小于总容量再操作 
		{
			x.push_back(w[i]);
			cw += w[i];
			cv += v[i];

			func(i + 1);

			x.pop_back();
			cw -= w[i];
			cv -= v[i];
		}
		
		// 加入减右枝操作
		if (cv + r > bestv)
		{
			func(i + 1);
		}
		r += v[i];
	}
}

int main()
{

	for (int v : v)
	{
		r += v;
	}
	func(0);
	cout << "cnt" <<  cnt << endl;
	cout << "选择的物品重量为:";
	for (int w : bestx)
	{
		cout << w << " ";
	}
	cout << endl;
	cout << "最优值为:" << bestv << endl;

	system("pause");
	return 0;

}

1.8 排列树解决N-皇后问题

8*8的格子中,任意两个棋子不能出现在同一行,同一列,同一条斜线上。

int cnt = 0;
// 8皇后问题
bool judge(int arr[], int i)
{
	for (int j = 0; j < i; j++)
	{
		if (i == j || arr[i] == arr[j] || abs(i - j) == abs(arr[i] - arr[j]))
		{
			return false;
		}
	}
	return true;
}

// 
void swap(int arr[], int i, int j)
{
	int temp = arr[i];
	arr[i] = arr[j];
	arr[j] = temp;
}

void func(int arr[], int i, int length)
{
	if (i == length)
	{
		cnt++;
		for (int j = 0; j < length; j++)
		{
			cout << arr[j] << " ";
		}
		cout << endl;
	}
	else
	{
		for (int k = i; k < length; ++k)
		{
			
			swap(arr, i, k);
			if (judge(arr, i))
			{
				func(arr, i + 1, length);   // 每次从上层遍历到下层的过程中,每向下一层,理解为选择每行每列的过程;
			}								// 遍历到第i层时候,都要判断0 -- i-1层,是否满足条件
			swap(arr, i, k);
		}
	}
}


int main()
{
	int arr[] = { 1,2,3,4 ,5,6,7,8};   // arr数组下标表示行,下标对应位置表示列;
	int n = 8;
	int length = sizeof(arr) / sizeof(arr[0]);
	func(arr, 0, length);

	cout << "cnt = " << cnt << endl;
	system("pause");
	return -1;
}

1.9 穷举法生成排列树

int arr[] = { 1,2,3 };
int length = sizeof(arr) / sizeof(arr[0]);
vector<int> vec;
int state[3] = { 0 };  // 1表示选择了,0表示未选择

void func(int i)
{
	if (i == length)
	{
		for (int v : vec)
		{
			cout << v << " ";
		}
		cout << endl;
	}
	else
	{
		for (int k = 0; k < length; ++k)
		{
			if (state[k] == 0)
			{
				state[k] = 1;
				vec.push_back(arr[k]);
				func(i + 1);
				vec.pop_back();
				state[k] = 0;
			}
		}
	}
}

int main()
{
	func(0);

	system("pause");
	return -1;
}

在这里插入图片描述

1.10穷举法func(k+1) 与 排列树中的fun(i+1)中,i 和 k区别?

穷举法中的k表示可选择的元素的起始下标,并且选择的数量不同,树的深度不同。
在这里插入图片描述
排列树中的i表示的是层数,排列树最后生成的是平衡树,树的深度相同。
在这里插入图片描述

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

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

相关文章

APSIM作物生长模型学习

由于研究需要&#xff0c;将对APSIM模型使用进行一定学习&#xff0c;特做此笔记&#xff0c;也供该模型的初学者共同进步。 首先是版本选择&#xff0c;这个模型发展较长&#xff0c;有经典的classic版本和次世代版本&#xff0c;而经过实际验证&#xff0c;次世代版本和经典版…

RHCSA 第六天笔记

网络配置 1&#xff0c;ip 命令 ip a 2,修改配置文件&#xff08;不推荐&#xff09; 3&#xff0c;nmcli命令 4&#xff0c;nmtui命令 5&#xff0c;cockpit 网络接口是指网络中的计算机或网络设备与其他设备实现通讯的进出口。这里&#xff0c;主要是指计算机的网络接口即网…

学习笔记之Vue组件化编程(二)

Vue组件化编程&#xff08;二&#xff09;Vue组件化编程一、模块与组件&#xff0c;模块化与组件化1.1 对组件的理解1.2 模块1.3 组件1.4 模块化1.5 组件化&#xff08;二&#xff09;Vue组件化编程 一、模块与组件&#xff0c;模块化与组件化 1.1 对组件的理解 在传统式编写…

Centos7下mysql8.0读写分离的配置

1.前言 1.关于读写分离的原理&#xff0c;这里不做太多赘述。主要从服务器去读取主服务的binlog日志&#xff0c;完成数据同步的过程。 这里我在mac开启了2个虚拟机&#xff0c;ip分别为192.168.31.109 ,192.168.31.208系统为centos 2.配置主从分离之前&#xff0c;需要安装…

第二十五讲:OSPF路由协议邻居认证配置

在相同OSPF区域的路由器上启用身份验证的功能&#xff0c;只有经过身份验证的同一区域的路由器才能互相通告路由信息。这样做不但可以增加网络安全性&#xff0c;对OSPF重新配置时&#xff0c;不同口令可以配置在新口令和旧口令的路由器上&#xff0c;防止它们在一个共享的公共…

android血量条的制作

最近&#xff0c;项目中需要用到血量条&#xff0c;想到血量条这东西&#xff0c;在游戏中经常见到。那么&#xff0c;再android开发中如何制作血量条呢&#xff1f;这里本人想到了两种方法&#xff0c;在网上找到一种最优方案。 方法一&#xff1a;用多张相同的图片拼凑而成的…

Docker安装nginx以及nginx-gui控制面板

一、安装nginx 1、搜索镜像 docker search nginx2、拉取镜像 docker pull nginx3、创建Nginx挂载配置文件 # 创建挂载目录 mkdir -p /install/nginx/conf mkdir -p /install/nginx/log mkdir -p /install/nginx/html# 生成容器 # 将容器nginx.conf文件复制到宿主机 # 将容器…

公网远程连接本地socket服务【内网穿透】

文章目录1. 配置本地socket服务2. 本地socket服务暴露至公网2.1 创建隧道映射9999端口2.2 获取公网地址3. 公网连接本地socket服务端1. 配置本地socket服务 Java 服务端demo环境 jdk1.8框架:springbootmaven开发工具:IDEA 在pom文件引入第三包封装的netty框架maven坐标 <…

【MyBatis】安装 + 框架搭建 + 优化 + 增删改查(全程一条龙服务讲解~)

目录 前言 一、准备工作 1.1、下载MyBatis 1.2、数据库设计 二、搭建框架 2.1、创建Maven项目 2.2、jar包、引入依赖 2.3、创建MyBatis核心配置文件 2.4、映射文件 2.5、通过junit测试功能 2.6、框架优化 三、增删改查优化 四、小结——注意事项 前言 本篇全程从0…

软件测试工程师的发展道路

最近看到一些测试朋友&#xff0c;对测试未来比较迷茫&#xff0c;不知该如何前行&#xff0c;无方向感。目前来看&#xff0c;业界目前存在一个普遍的矛盾&#xff0c;一方面很多人会觉得测试没有发展前途&#xff0c;另一方面&#xff0c;又有非常多的企业急需专业的测试人员…

React学习05-React Router 5

React Router 5 相关理解 SPA 单页Web应用&#xff08;single page web application&#xff0c;SPA&#xff09;。整个应用只有一个完整的页面。点击页面中的链接不会刷新页面&#xff0c;只会做页面的局部更新。数据都需要通过ajax请求获取, 并在前端异步展现。 路由 什么…

全球十大数据安全事件

2021年&#xff0c;数据隐私泄露事件频发&#xff0c;涉及面广&#xff0c;影响力大&#xff0c;企业因此陷入数据保护合规与社会舆情压力的双重危机。近日&#xff0c;有国外媒体梳理了2021年十大数据泄密事件&#xff0c;并对事件进行了点评分析&#xff0c;可供读者参考。据…

第二十九讲:神州路由器DHCP协议的配置

实验拓扑图如下所示 操作步骤&#xff1a; 步骤1&#xff1a;按照图1&#xff0c;正确连接拓扑结构图。 步骤2&#xff1a;为路由器设置主机名称和配置接口IP地址。 Router>enable &#xff01;进入特权模式 Router #config &a…

MySQL高级【SQL性能分析】

目录 1&#xff1a;SQL性能分析 1.1&#xff1a;SQL执行频率 1.2&#xff1a;慢查询日志 1.3&#xff1a;profile详情 1.4&#xff1a;explain 1&#xff1a;SQL性能分析 1.1&#xff1a;SQL执行频率 MySQL 客户端连接成功后&#xff0c;通过 show [session|global] sta…

2022年的5G行业:“5G+”很火,5G网络迟迟未能普及

作者 | 曾响铃 文 | 响铃说 2022年&#xff0c;5G行业依旧是如火如荼地发展&#xff0c;5G技术继续深刻地改变着我们的生活与生产&#xff0c;影响社会经济发展的方方面面。 回顾过去的一年&#xff0c;5G行业有看点&#xff0c;也有疑虑。 5G网络已基本覆盖全国。截至 202…

PaddleNLP开源基于UIE的情感分析,解决小样本难题,助力客户意见洞察与舆情分析!

情感分析&#xff08;Sentiment Analysis&#xff09;是近年来国内外研究的热点&#xff0c;旨在对带有情感色彩的主观性文本进行分析、处理、归纳和推理。情感分析具有广泛的应用场景&#xff0c;可被应用于消费决策、舆情分析、个性化推荐等领域。 如上图所示&#xff0c;情…

移位操作符、位操作符,原码、反码、补码

整数的二进制的表达形式有3种。原码反码补码下面我们举一个例子吧十进制的2原码&#xff1a;00000000000000000000000000000010&#xff08;常见的形式&#xff09;反码&#xff1a;00000000000000000000000000000010补码&#xff1a;00000000000000000000000000000010小结 正整…

k8s集群部署02

k8s集群部署02k8s集群部署02仍然报错若镜像拉取过慢原因k8s集群部署02 一、pod基本操作Pod是可以创建和管理Kubernetes计算的最小可部署单元&#xff0c;一个Pod代表着集群中运行的一个进程&#xff0c;每个pod都有一个唯一的ip。一个pod类似一个豌豆荚&#xff0c;包含一个或…

Hack the Box CTF 比赛 简单难度 XOR 密码学题目 Crypto 流程| Multikey Walkthrough

这是近期参加HTB夺旗战时遇到的一道难度为简单的密码学Crypto题目。但是我觉得挺有意思&#xff0c;就做下记录。 1. 题目&#xff1a; 题干没有太多的内容&#xff0c;就是一段python程序&#xff0c;和一个output的加密结果&#xff0c;如下。 Python&#xff1a; import …

【数据结构】手推堆实现,拳打堆排序,脚踩Top-k

目录一.完全二叉树的顺序结构二.堆的概念及结构三.堆的实现1.堆向下调整2.向下调整建堆3.向下调整建堆时间复杂度4.堆的插入&#xff08;向上调整&#xff09;5.向上调整建堆6.向上调整建堆时间复杂度7.堆的删除8.堆的代码实现四.Top-K问题五.堆排序一.完全二叉树的顺序结构 堆…