<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>Basic Algorithm &#8211; HU Xiaoxu</title>
	<atom:link href="https://blog.ihuxu.com/category/computer-science/data-structure-and-algorithm/basic-algorithm/feed/" rel="self" type="application/rss+xml" />
	<link>https://blog.ihuxu.com</link>
	<description>a software engineer&#039;s blog</description>
	<lastBuildDate>Sat, 21 Jun 2025 01:44:23 +0000</lastBuildDate>
	<language>en-US</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	<generator>https://wordpress.org/?v=6.6.2</generator>
	<item>
		<title>递归</title>
		<link>https://blog.ihuxu.com/recursive/</link>
					<comments>https://blog.ihuxu.com/recursive/#respond</comments>
		
		<dc:creator><![CDATA[HU Xiaoxu]]></dc:creator>
		<pubDate>Wed, 04 Dec 2019 16:38:43 +0000</pubDate>
				<category><![CDATA[Basic Algorithm]]></category>
		<category><![CDATA[Computer Science]]></category>
		<category><![CDATA[Data Structure and Algorithm]]></category>
		<category><![CDATA[Original]]></category>
		<category><![CDATA[Recursive]]></category>
		<guid isPermaLink="false">http://blog.ihuxu.com/?p=11712</guid>

					<description><![CDATA[递归是什么？ 递归（英语：Recursion），又译为递回，在数学与计算机科学中，是指在函数的定义中使用函数自身的方法 。 维基百科 简单说，就是自身调用自身。 为什么使用递归？ 往往面对一类问题时，如果它的规模足够小或者说达到既定的边界条件时，我们可以直接获取答案。但是，当这类问题的规模比较大时，却往往无法直接获取答案。那么，这个时候就可以通过“自身调用自身”的方式，来不断地减小问题的规模，直到问题的规模被缩减到足够小时，直接将答案返回上层的调用者，最终获取到原问题的解。如果将求解的过程逆过来，那么就是所谓的递推。 通过这种方式，我们可以写出“优雅”的代码去解决规模比较大的问题。进而，避免了通过递推的方式，在每一次递推时产生的复杂的条件判断的问题。 上文中提到经过递归调用，会不断地减小问题的规模，有些作者认为这是一种减治法。 递归的特性 自身调用自身 在上文中，已经提到了这个特性，而且也非常好理解，不再赘述。 回溯时还原现场 在使用递归方法时，其中有一个不得不提的特性——回溯时还原现场。 通过递归调用，程序将执行到极限触达边界条件时，就需要将当前层的调用跳出“调用栈”，在跳出“调用栈”时，需要将一些状态信息还原到上一层场景所属的状态，即所谓的回溯时还原场景。 举个例子 有一颗二叉树，求解。代表以root为根的树的最大高度，即。 首先，我们要定义一个递归函数。在定义函数之前，我们要明确两个重要的事情： 函数的含义，代表了递归函数能为我们解决什么样的问题。在这里，我们定义函数的含义为求解某一个子树的高度。 函数的参数，代表了递归函数求解的问题的规模。在这里，我们定义函数的参数为当前需要遍历的节点 —— 以当前节点为根的子树（问题规模）。 在明确了这两件事情之后，面对一个规模较大的、复杂的问题就会变的简单得多。 下面，我们来看看在使用递归函数解决当前问题的整个过程。 递归求解树的最大高度 记忆化递归 通过上面简单的例子，我们了解了如何通过递归解决一个较大规模的问题。但是，我们会发现，使用递归函数解决的每个子问题的解仅仅被使用了一次。然而，在某些复杂的场景，子问题的解可能被使用若干次。那么这个时候，可以考虑加上备忘录法进行优化，即记忆化递归。 递归 —— 这种方法的思考方式是自顶向下的，也就是说符合我们常人在解决问题时的正向思考的过程。这与动态规划 —— 自底向上的方法恰恰相反（记忆化递归与动态规划的问题，我将在后续的篇章中讲解）。 三个经典问题 递归实现指数型枚举 从 ~ 这 个整数中随机选取任意多个，输出所有可能的选择方案。 在最开始学习算法时，面对这种类似的问题我思考了好久。当打开大牛的代码时，才恍然大悟，原来可以用这么简洁的代码解决 —— 递归。 那么，先来分析下这个“小”问题。我们需要在一个序列中随机选取任意多个数字，也就是说每一个数字都有两种可能——选择或不选择。那么，一共的可能的方案总数即 —— 那么，根据上文提到的方案，我们先来确认两件事情： 明确这两件事情之后，我们还需要一个额外的全局变量（），用来保留递归过程中产生的状态信息 —— 当前调用栈被选中的数字。 时间复杂度 每一个数字有两种可能 —— 选择或不选择 递归实现组合型枚举 从 ~ 这 个整数中随机选出 个，输出所有可能的选择方案。 这个问题与上面的问题的唯一区别是限定了选定元素的个数。 很自然地我们可以想到，在输出答案时我们只需要判断下结果集变量 —— 的大小是不是即可。当然，我们也可以在每次搜索时进行判断 —— 剪枝法，这样可以带来性能上的提升。 时间复杂度 我们可以通过判断当前调用栈中的中的数字个数如果大于，或者加上剩余的所有数字都达不到个的话，那么直接返回，跳出调用栈，进行其他情况的搜索。这样，避免了无谓的搜索，只关心满足条件的结果集。所以时间复杂度就从 降到了 。 递归实现排列型枚举 把 ~<div class="read-more"><a class="btn read-more-btn" href="https://blog.ihuxu.com/recursive/">Read More</a></div>]]></description>
		
					<wfw:commentRss>https://blog.ihuxu.com/recursive/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		<enclosure url="https://qoogle.top/wp-content/uploads/2019/12/演示文稿-1.mp4" length="1596806" type="video/mp4" />

			</item>
		<item>
		<title>动态规划与分治法的思考</title>
		<link>https://blog.ihuxu.com/thoughts-on-dynamic-planning-and-dividing-and-conquer-method/</link>
					<comments>https://blog.ihuxu.com/thoughts-on-dynamic-planning-and-dividing-and-conquer-method/#respond</comments>
		
		<dc:creator><![CDATA[HU Xiaoxu]]></dc:creator>
		<pubDate>Tue, 01 Jan 2019 10:55:27 +0000</pubDate>
				<category><![CDATA[Basic Algorithm]]></category>
		<category><![CDATA[Computer Science]]></category>
		<category><![CDATA[Data Structure and Algorithm]]></category>
		<category><![CDATA[Dynamic Programming]]></category>
		<category><![CDATA[Original]]></category>
		<guid isPermaLink="false">http://blog.ihuxu.com/?p=9939</guid>

					<description><![CDATA[如果一个问题具有最优子结构的性质，此外子问题具有重叠性质，那么可以采用自底向上的动态规划的思路进行求解。 同时，往往可以用递归的方式自顶向地进行求解，即分治法。 如果用分治法去求解这个问题时，能够利用备忘录法进行避免对于子问题的重复计算，那么其计算的效率可以和动态规划的计算效率相比。]]></description>
		
					<wfw:commentRss>https://blog.ihuxu.com/thoughts-on-dynamic-planning-and-dividing-and-conquer-method/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>分治法与回溯法的思考</title>
		<link>https://blog.ihuxu.com/thoughts-on-divide-and-conquer-and-backtracking/</link>
					<comments>https://blog.ihuxu.com/thoughts-on-divide-and-conquer-and-backtracking/#respond</comments>
		
		<dc:creator><![CDATA[HU Xiaoxu]]></dc:creator>
		<pubDate>Tue, 01 Jan 2019 10:39:37 +0000</pubDate>
				<category><![CDATA[Basic Algorithm]]></category>
		<category><![CDATA[Computer Science]]></category>
		<category><![CDATA[Data Structure and Algorithm]]></category>
		<category><![CDATA[Back Tracking]]></category>
		<category><![CDATA[DFS]]></category>
		<category><![CDATA[Divide and Conquer]]></category>
		<category><![CDATA[Original]]></category>
		<guid isPermaLink="false">http://blog.ihuxu.com/?p=9935</guid>

					<description><![CDATA[共同的递归性质 在广义上来说，所有递归的算法都属于分治法。无非是将问题分解成一个规模更小的问题，还是将问题分解成若干个，甚至和输入规模多项式级别的子问题。那么对于前者，有些作者称作是减治法，后者称作分治法。 那么对于回溯法（以深度优先搜搜方式进行）来说，目前为止我见过的都是通过递归的形式来实现的，那么从这个意义上来讲，回溯算法就是分治法的一种。 回溯状态的有无 再说，之所以称作是回溯法，是因为在搜索的过程中需要回溯到问题的某个状态，所以这往往需要保存回溯时的一些状态属性。然而，分治法通常并不需要考虑回溯状态的保存。 分解问题的规模 分治法往往是将问题分解成若干个子问题的形式，然而回溯法往往是将问题分解成规模更小的一个子问题。由于分治法将问题分解成若干个子问题，故当前问题的解需要依赖于若干个子问题，也就是若干个搜索路径的解，所以重点在于如何合并子问题的解；而然回溯法问题规模就为1个，当前的搜索路径的问题触碰到边界条件时，即可得到当前搜索路径的问题的解，并不需要合并，或者说合并很简单。 分治法可变成复杂的回溯法 如果分治法保留了每个问题的可回溯的状态，并利用了这些状态，尽管这非常的不容易实现，因为某个问题的回溯的状态可能需要依赖很多其子问题的状态。如果成功保存了这些回溯状态，那么这个时候，分治法就成了复杂的回溯法。]]></description>
		
					<wfw:commentRss>https://blog.ihuxu.com/thoughts-on-divide-and-conquer-and-backtracking/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>一只青蛙跳出来的分治法、回溯法与动态规划</title>
		<link>https://blog.ihuxu.com/divide-and-conquer-backtracking-and-dynamic-programming-from-a-frog-jumping-out/</link>
					<comments>https://blog.ihuxu.com/divide-and-conquer-backtracking-and-dynamic-programming-from-a-frog-jumping-out/#comments</comments>
		
		<dc:creator><![CDATA[HU Xiaoxu]]></dc:creator>
		<pubDate>Fri, 28 Dec 2018 07:40:53 +0000</pubDate>
				<category><![CDATA[Basic Algorithm]]></category>
		<category><![CDATA[Computer Science]]></category>
		<category><![CDATA[Data Structure and Algorithm]]></category>
		<category><![CDATA[Dynamic Programming]]></category>
		<category><![CDATA[Back Tracking]]></category>
		<category><![CDATA[Divide and Conquer]]></category>
		<category><![CDATA[Original]]></category>
		<guid isPermaLink="false">http://blog.ihuxu.com/?p=9925</guid>

					<description><![CDATA[从2018年7月份开始，基础薄弱的我从0开始刷LeetCode题目。目的性很明确，也很简单——就是为了提高解决问题的思考实践能力，也为了提升自己的核心竞争力。也许，牛人会觉得这并不算什么竞争力。是的，我同意的。但，这是我目前能做的比较基础的事情罢了。 迄今（2018年12月28日）为止，已经刷了108道题目。顺序基本上是按照出现的频率（Frequency）来刷的，这个频率在LeetCode上需要订阅后才可以看得到。那么在刷了108道题目后，有那么一些题目会觉得“似曾相识”了，也会有一种触类旁通的感觉了。所以，我觉得应该适当放慢刷题的速度，同时做做总结了。 所以，计划了一项视频解说计划，在YouTubeh和B站都建立了《小旭解说算法之路》的频道，欢迎订阅，多多提建议。 那么，进入正题。经过了108道题的历练之后，我来说说对于分治法、回溯法和动态规划的理解。 我觉得他们三者是一个相互有交集的概念，并不是相互完全独立的。至于为什么不是完全独立的，在分别说说这三种方法的解决思路后，我们再终结一下。 分治法（Divide and Conquer） 分治法是解决规模庞大的问题的很好的思路，他通过降低问题的规模，形成若干个规模更小但形式相同的子问题，进行递归求解。在求解过后，将各个子问题的解合并起来，形成原问题的解。 那么它的大致流程主要分成三步： 所以，明确了三步之后，还要明确一件事件——实现方式：递归法。 分治法一般来说会采用递归法来进行实现，当然，利用迭代法（比如for、while）也是可以的。 所以，我们往往看到的递归算法从广义上来说都是分治法。无非就是有些递归算法将问题分解了若干个子问题，然而有些递归算法将问题分解成了一个子问题。那么有些作者会称作前者是分治法，后者是减治法。 其实，这个概念真的非常非常重要。在面对很多问题的时候，都可以用这种思路去思考。那么其中思考的一个非常重要的一点就是递归算法中的边界（跳出）条件的判定。 只要，我们想明白了求解子问题过程中的边界条件，那么问题就会很清晰，并且很容易写出程序来。否则，模糊的边界条件，会导致整个递归算法进入到“死循环”的尴尬地步。 举个例子 青蛙🐸跳台阶的问题：一只青蛙一次可以跳上1级台阶，也可以跳上2级。求该青蛙跳上一个n级的台阶总共有多少种跳法？ 那么如果我们用分治法的思路思考的话，这道题真是非常非常容易理解。 首先，当青蛙在面对第一个台阶时，他只有两种选择——跳一步还是跳两步。如果我们定义f(n)代表青蛙跳跃到n层台阶一共的方法数，那么我们可以将问题进行分解两个规模更小，但形式相同的问题： 其中f(n &#8211; 1)是青蛙选择跳一步后，剩下的子问题，同理f(n &#8211; 2)是青蛙选择跳两步后剩下的子问题。这样，我们就把问题进行了分解。 下面再谈谈如何解决，正如上面谈到的解决步骤，如果规模足够小那么直接返回，否则继续降低规模进行递归求解。这时，就是我们要确定边界条件——即当n = 1 和n = 2时的情况。 在明确了边角条件后，合并就非常的简单，也就是简单的相加即可了。 那么代码写出来是什么样子呢？ 回溯法（Backtracking） 回溯法，我理解应该也可以叫做深度优先搜索（Depth-First Search）。所以，他是一种搜索算法。 既然谈到搜索，往往这里面会面临选择的情景。以那个青蛙为例，当面对第一个台阶时，他有两个选择。当他选择一种选择后，将“义无反顾”的一条道走下去，每层都会进行一次选择，直到走到地n层位置时。这时，青蛙已经触碰到了边界，并得到了一种方案，之后青蛙会返回到最近的上一次选择时的情景，选择第二种情况继续走下去。以此往复，直到搜索全部的情景。 是的，这非常的抽象。我们来看看用二叉树来描述运动轨迹是怎么样的。我们假设n = 3。 颜色 左右子树 回溯 如下图中红框标记的位置就是回溯到某一个情况。 如果你理解了下图的运动轨迹，我想差不多对于回溯的搜索过程就基本了解了。所以，你可以找到其他回溯点么？ 理解了上图的运动轨迹后，那么，代码是什么样子呢？ 边界条件 再说更重要的选择的两个基本点 以此进行递归搜索（深度优先搜索DFS），在搜索到边界时进行回溯，以此往复直到搜索到所有情况为止。 动态规划（Dynamic Programming） 动态规划有两个重要的基本性质 最优子结构 如果一个问题的最优解包含了其中子问题的最优解，那么称其具有最优子结构的性质。 什么意思？青蛙在面对n个台阶时的解决方案数是f(n)，那么我们知道f(n) = f(n &#8211; 1) + f(n &#8211; 2)。其中的f(n &#8211; 1)与f(n &#8211; 2)就是两个子问题的最优解，此时我们可以理解成一个问题的最优解包含了其子问题的最优解，那么这个时候这种问题具有了最优子结构性质。 重叠子问题 这个性质，在我理解是对于上文提到的子问题的补充说明。当解决一个问题时，往往需要依赖于其更小规模的子问题的解，甚至是同时依赖于若干个规模更小的子问题的解，即子问题是被（重复）包含于比其更大的问题中的，所以他是具有重叠子问题的性质。 在这里，多提出一句，这个子问题是在解决当前问题时需要依赖的。即，只有计算了子问题，父问题才可能被求解。这是和贪心算法的重要区别所在。 状态转移方程<div class="read-more"><a class="btn read-more-btn" href="https://blog.ihuxu.com/divide-and-conquer-backtracking-and-dynamic-programming-from-a-frog-jumping-out/">Read More</a></div>]]></description>
		
					<wfw:commentRss>https://blog.ihuxu.com/divide-and-conquer-backtracking-and-dynamic-programming-from-a-frog-jumping-out/feed/</wfw:commentRss>
			<slash:comments>5</slash:comments>
		
		
			</item>
	</channel>
</rss>
