算法入门<二>:分治算法之汉诺塔问题及递归造成的栈溢出

news2024/12/23 20:46:18

1、分治算法

  分治(divide and conquer),全称分而治之,是一种非常重要且常见的算法策略。分治通常基于递归实现,包括“分”和“治”两个步骤。

  • (划分阶段):递归地将原问题分解为两个或多个子问题,直至到达最小子问题时终止。
  • (合并阶段):从已知解的最小子问题开始,从底至顶地将子问题的解进行合并,从而构建出原问题的解。

一个问题是否适合使用分治解决,通常可以参考以下几个判断依据。

  • 问题可以分解:原问题可以分解成规模更小、类似的子问题,以及能够以相同方式递归地进行划分。
  • 子问题是独立的:子问题之间没有重叠,互不依赖,可以独立解决。
  • 子问题的解可以合并:原问题的解通过合并子问题的解得来。

  分治不仅可以有效地解决算法问题,往往还可以提升算法效率。在排序算法中,快速排序、归并排序、堆排序相较于选择、冒泡、插入排序更快,就是因为它们应用了分治策略。

2、汉诺塔问题

2.1 传说

  大梵天创造世界的时候做了三根金刚石柱子,在一根柱子上从下往上按照大小顺序摞着64片黄金圆盘。大梵天命令婆罗门把圆盘从下面开始按大小顺序重新摆放在另一根柱子上。并且规定,在小圆盘上不能放大圆盘,在三根柱子之间一次只能移动一个圆盘。 简化后类似如下:
在这里插入图片描述

2.2 分治策略

  解决汉诺塔问题的分治策略:将原问题f(n)划分为两个子问题f(n-1)和一个子问题f(1),并按照以下顺序解决这三个子问题。

  1. 将n-1个圆盘借助 C 从 A 移至 B 。
  2. 将剩余1个圆盘从 A 直接移至 C 。
  3. 将n-1个圆盘借助 A 从 B 移至 C 。

  对于这两个子问题 f(n-1),可以通过相同的方式进行递归划分,直至达到最小子问题 f(1)。而f(1)的解是已知的,只需一次移动操作即可。
在这里插入图片描述

2.3 代码实现

/* 求解汉诺塔问题 f(i) */

/* 移动一个圆盘 */
void move(vector<int>& src, vector<int>& tar) 
{
	// 从 srcA 顶部拿出一个圆盘
	int pan = src.back();
	src.pop_back();
	// 将圆盘放入 tarC 顶部
	tar.push_back(pan);
}

void dfs(int nSize, vector<int>& srcA, vector<int>& bufB, vector<int>& tarC,int& nMoveTimes)
{
	// 若 srcA 只剩下一个圆盘,则直接将其移到 tarC
	if (nSize == 1)
	{
		nMoveTimes++;
		move(srcA, tarC);
		return;
	}
	// 子问题 f(i-1) :将 srcA 顶部 i-1 个圆盘借助 tarC 移到 bufB
	dfs(nSize - 1, srcA, tarC, bufB, nMoveTimes);
	// 子问题 f(1) :将 srcA 剩余一个圆盘移到 tarC
	move(srcA, tarC);
	// 子问题 f(i-1) :将 bufB 顶部 i-1 个圆盘借助 srcA 移到 tarC
	dfs(nSize - 1, bufB, srcA, tarC, nMoveTimes);
}

void solveHanota(vector<int>& A, vector<int>& B, vector<int>& C, int& nMoveTimes)
{
	int nSize = A.size();
	// 将 A 顶部 n 个圆盘借助 B 移到 C
	dfs(nSize, A, B, C, nMoveTimes);
}

int main()
{
	int nObject = 25;
	vector<int> vecObject;
	for (int i = nObject; i > 0; i--)
	{
		vecObject.push_back(i);
	}

	int nMoveTimes = 0;
	vector<int> objectB;
	vector<int> objectC;
	solveHanota(vecObject, objectB, objectC, nMoveTimes);

	std::cout << "放置圆盘" << nObject << "个,共计需要移动" << nMoveTimes << "次!" << endl;

	return 0;
}
放置圆盘25个,共计需要移动16777216次!

2.4 代码漏洞

  当盘子数量为64的话,一共需要移动约1800亿亿步(18,446,744,073,709,551,615),才能最终完成整个过程。即使借助于计算机,假设计算机每秒能够移动100万步,那么约需要18万亿秒,即58万年。将计算机的速度再提高1000倍,即每秒10亿步,也需要584年才能够完成。所以上述解法无法处理大数据量问题

3、递归导致栈溢出

  递归算法在数学问题上会经常出现,但运用不当会出问题。有的是运算时间太长如上面的汉诺塔问题,有的则是栈溢出,例如下面代码。我们着重说一下栈溢出问题。

// 递归求和函数当n=4750会出现栈溢出问题
void Sum(int& nSum, int n)
{
	if (n == 0)
	{
		return;
	}

	nSum += n;
	n--;

	Sum(nSum, n);
}
// 斐波那契数列 0,1,1,2,3,5,8…… 递归树 时间复杂度高 会卡死
unsigned int fib(int n)
{
	if (n == 1 || n == 2)
	{
		return n - 1;
	}

	return fib(n - 2) + fib(n - 1);
}
// 阶乘 时间复杂度高 卡死
unsigned int factorialRecur(unsigned int n)
{
	if (n == 0)
	{
		return 1;
	}

	int count = 0;
	for (int i = 0; i < n; i++)
	{
		count += factorialRecur(n - 1);
	}
	return count;
}

3.1 为什么会栈溢出?

  计算机的内存可以分为三个区域:栈区,堆区,静态区。它们在存储使用时遵循不同的规则,并且存储的内容也不同。其中栈内存速度最快但空间很小一般只有几兆M大小,而我们每一次递归时,上一次的递归程序仍然没有结束,也就是上一次递归的函数仍然占据着内存栈区的空间; 递归调用函数次数太多栈就会溢出。

在这里插入图片描述

3.2 解决方式

  对于栈溢出的问题一般通过减少递归的层数来解决。比如下面的求和问题。

// 递归求和函数当n=4750会出现栈溢出问题
void Sum(int& nSum, int n)
{
	if (n == 0)
	{
		return;
	}

	nSum += n;
	n--;

	Sum(nSum, n);
}


// 迭代求和  可正常计算
unsigned int Sum(int n)
{
	int nSum = 0;
	for (int a = 1; a <= n; a++)
	{
		nSum += a;
	}

	return nSum;
}

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

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

相关文章

【C语言】指针篇-精通库中的快速排序算法:巧妙掌握技巧(4/5)

&#x1f308;个人主页&#xff1a;是店小二呀 &#x1f308;C语言笔记专栏&#xff1a;C语言笔记 &#x1f308;C笔记专栏&#xff1a; C笔记 &#x1f308;喜欢的诗句:无人扶我青云志 我自踏雪至山巅 文章目录 一、回调函数二、快速排序(Qsort)2.1 Qsort参数部分介绍2.2 不…

数据仓库和数据仓库分层

一、数据仓库概念 数据仓库(Data Warehouse)&#xff0c;可简写为DW或DWH。数据仓库&#xff0c;是为企业所有级别的决策制定过程&#xff0c;提供所有类型数据支持的战略集合。它是单个数据存储&#xff0c;出于分析性报告和决策支持目的而创建。 为需要业务智能的企业&#…

计算机网络4——网络层4内部路由选择协议

文章目录 一、有关路由选择协议的几个基本概念1、理想的路由算法2、分层次的路由选择协议 二、内部网关协议 RIP1、协议 RIP 的工作原理2、特点3、距离向量算法4、坏消息传播慢 三、内部网关协议 OSPF1、基本特点2、OSPF 的五种分组类型 本节将讨论几种常用的路由选择协议&…

uniapp 自定义 App启动图

由于uniapp默认的启动界面太过普通 所以需要自定义个启动图 普通的图片不可以过不了苹果的审核 所以使用storyboard启动图 生成 storyboard 的网站&#xff1a;初雪云-提供一站式App上传发布解决方案

从零入门区块链和比特币(第一期)

欢迎来到我的区块链与比特币入门指南&#xff01;如果你对区块链和比特币感兴趣&#xff0c;但不知道从何开始&#xff0c;那么你来对地方了。本博客将为你提供一个简明扼要的介绍&#xff0c;帮助你了解这个领域的基础知识&#xff0c;并引导你进一步探索这个激动人心的领域。…

使用RTSP将笔记本摄像头的视频流推到开发板

一、在Windows端安装ffmpeg 1. 下载ffmpeg:下载ffmpeg 解压ffmpeg-master-latest-win64-gpl.zip bin 目录下是 dll 动态库 , 以及 可执行文件 ;将 3 33 个可执行文件拷贝到 " C:\Windows " 目录下 ,将所有的 " .dll " 动态库拷贝到 " C:\Windows\Sy…

java集合框架中的Map和Set的使用方式

目录 一、Map的使用方法说明 put&#xff08;&#xff09;&#xff1a; GetOrDefault()&#xff1a; containsKey()与containsVal&#xff08;&#xff09;&#xff1a; keySet()与m.values()&#xff1a; 二、Set的使用方法说明 add(): iterator()---->迭代器 一、M…

19 做好微服务间依赖的治理和分布式事务

在前两讲里&#xff0c;分别从微服务的对外接口、消息消费以及微服务自身的相关编码规范上阐述了“防备上游、做好自己”这两个准则如何落地。 在本讲里&#xff0c;将会讲解为什么要“怀疑下游”&#xff0c;以及有哪些手段可以落地此条准则。此外&#xff0c;还会介绍在进行…

每日OJ题_DFS爆搜深搜回溯剪枝②_力扣526. 优美的排列

目录 力扣526. 优美的排列 解析代码 力扣526. 优美的排列 526. 优美的排列 难度 中等 假设有从 1 到 n 的 n 个整数。用这些整数构造一个数组 perm&#xff08;下标从 1 开始&#xff09;&#xff0c;只要满足下述条件 之一 &#xff0c;该数组就是一个 优美的排列 &#…

Content type ‘application/json;charset=UTF-8‘ not supported异常的解决过程

1.首先说明开发场景 *就是对该json格式数据传输到后台 后台实体类 import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableName; import com.fasterxml.jackson.annotation.JsonIgnore; import lombok.Data; import org.sp…

Linux搭建靶场

提前准备&#xff1a; 文章中所使用到的Linux系统&#xff1a;Ubantu20.4sqlilabs靶场下载地址&#xff1a;GitHub - Audi-1/sqli-labs: SQLI labs to test error based, Blind boolean based, Time based. 一. 安装phpstudy phpstudy安装命令&#xff1a;wget -O install.sh h…

《MySQL对库的基本操作》

文章目录 一、查看数据库列表查看数据库中的所有表想知道当前处于哪个数据库里 二、创建一个数据库三、删除一个数据库知道两个集1.字符集2.校验集修改数据库的字符集和编码集 不同的校验码对数据库的影响四、数据库的备份与恢复注意事项&#xff1a;备份数据库中的表 总结 一、…

Lan仿朋友圈系统源码,用于表白墙等微商相册,商品图册等

这是一套基于PHP开发的Lan仿朋友圈系统开源&#xff0c;适用于表白墙、微商相册、商品图册等场景。 下 载 地 址 &#xff1a; runruncode.com/php/19750.html 主要功能包括&#xff1a; - 支持前端用户注册和消息提示。 - 用户注册时可设置必须验证邮箱账号&#xff0c;以…

【C++】学习笔记——类和对象_5

文章目录 二、类和对象14. 日期类的实现15. const成员16. 取地址重载17. 再谈构造函数初始化列表 18. explicit关键字19. static成员 未完待续 二、类和对象 14. 日期类的实现 上一篇我们已经大致将日期类的重要功能都给实现了&#xff0c;这节将会对日期类进行完善&#xff…

Linux 端口复用:SO_REUSEPORT

文章目录 前言一、BSD socket1.1 简介1.2 SO_REUSEADDR1.2.1 3-way or 4-way handshake1.2.2 SO_LINGER 1.3 SO_REUSEPORT 二、Connect() Returning EADDRINUSE三、Multicast Addresses四、Linux4.1 Linux < 3.94.2 Linux > 3.9 五、Linux SO_REUSEPORT socket option六、…

python安卓自动化pyaibote实践------学习通自动刷课

前言 欢迎来到我的博客 个人主页:北岭敲键盘的荒漠猫-CSDN博客 本文是一个完成一个自动播放课程&#xff0c;避免人为频繁点击脚本的构思与源码。 加油&#xff01;为实现全部电脑自动化办公而奋斗&#xff01; 为实现摆烂躺平的人生而奋斗&#xff01;&#xff01;&#xff…

【吊打面试官系列】Java高并发篇 - 为什么 wait 和 notify 方法要在同步块中调用?

大家好&#xff0c;我是锋哥。今天分享关于 【为什么 wait 和 notify 方法要在同步块中调用&#xff1f;】面试题&#xff0c;希望对大家有帮助&#xff1b; 为什么 wait 和 notify 方法要在同步块中调用&#xff1f; Java API 强制要求这样做&#xff0c;如果你不这么做&#…

论文精读-基于FPGA的卷积神经网络和视觉Transformer通用加速器

论文精读-基于FPGA的卷积神经网络和视觉Transformer通用加速器 优势&#xff1a; 1.针对CNN和Transformer提出了通用的计算映射&#xff08;共用计算单元&#xff0c;通过不同的映射指令&#xff0c;指导数据通路和并行计算&#xff09; 2.非线性与归一化加速单元&#xff0…

windows和mac 电脑 部署Ollama

官网地址&#xff1a;https://ollama.com/ github地址&#xff1a;https://github.com/ollama/ollama 一、windows下 https://github.com/ollama/ollama 安装大模型 ollama run llama3 下载的大模型地址&#xff1a; C:\Users\dengg\.ollama 4.34G

数据结构––队列

1.队列的定义 2.队列的分类 2.1循环队 2.2链式队 3.队列的实现 3.1循环队 3.1.1声明 typedef int QDataType; #define MAXSIZE 50 //定义元素的最大个数 /*循环队列的顺序存储结构*/ typedef struct {QDataType *data;int front; //头指针int rear; //尾指针 }Queue;…