算法

前言

  1. 程序 = 数据结构 + 算法 -----> 程序 = 数据结构 + 算法 + 程序设计方法 + 语言和环境
  • 算法是灵魂,来解决“做什么”和“怎么做”的问题
  • 数据结构是加工对象
  • 语言是工具
  • 编程需要采用合适的方法
  1. 计算机算法可分为两大类:
  • 数值运算算法:用于求解数值
  • 非数值运算算法:用于事务管理领域
  1. 算法是一个从具体到抽象的过程;算法要求能够解决一类问题,而不是一个问题。

下面说说时间复杂度:
1)一个算法中的语句执行次数称为语句频度时间频度,记为T(n):n称为问题的输入规模,当n不断变化时,时间频度T(n)也会不断变化。
2)一个算法的“运行工作量”通常是随问题规模的增长增长,所以应该用“增长趋势”来作为比较不同算法优劣的准则。
3)在一般情况下,算法中的基本操作重复执行的次数是问题规模n的某个函数,用T(n)表示,若有某个辅助函数f(n),使得当n趋近于无穷大时,T(n)/f(n)的极限值为不等于零的常数,则称f(n)是T(n)的同数量级函数;或者说,随着随着问题规模n的增长,算法执行时间的增长率和f(n)的增长率相同。于是,T(n) = O(f(n)),称O(f(n))为算法的渐进时间复杂度,简称时间复杂度。(同样,也可以定义出空间复杂度的概念。 )


那么如何估算算法的时间复杂度呢?
任何一个算法都是由一个“控制结构”和若干“原操作”组成的,所以可以将一个算法的执行时间看做所有原操作的执行时间之和,即:
∑ ( 原 操 作 i 的 执 行 次 数 ∗ 原 操 作 i 的 执 行 时 间 ) \sum_{} (原操作i的执行次数 * 原操作i的执行时间) (ii)
显然,算法的执行时间与所有原操作的执行次数之和成正比。对于所研究的问题来说,从算法中选取一种基本操作的原操作,以该基本操作在算法中重复执行的次数作为算法时间复杂度的依据。以这种衡量效率的办法所得出的不是时间量,而是一种增长趋势的量度。它与硬件环境无关,只暴露算法本身执行效率的优劣。


下面是几个时间复杂度的实例:
在这里插入图片描述
图中的n表示对应算法的问题规模。下面看一下时间复杂度为O(n3)的矩阵乘法运算:

//a、b、c均为n阶方阵,且c是a和b的乘积
void Mult_matrix(int c[][], int a[][], int b[][], int n) 
{
	for(int i = 1; i <= n; i++)
		for(int j = 1; j <= n; j++)
		{
			c[i,j] = 0;
			for(int k = 1; k <= n; k++)
				c[i,j] += a[i, k] * b[k, j];
		}
}

原操作c[i,j] += a[i, k] * b[k, j];的执行次数是n3,所以我们可以认为该算法的时间复杂度为O(n3)(注意:时间复杂度时取决于最深循环内包含的某个基本操作的语句的重复执行次数。这一重复次数就是语句的“频度”。)

一. 算法思想的大概划分

枚举算法思想

  1. 算法思想:将问题所有的可能答案一一列举,然后根据条件判断是否合适,合适就保留,不合适就丢弃。
  2. 算法实现:通常使用循环来实现。
    在这里插入图片描述
  • 确定枚举对象:题解的可能范围是什么?(不能遗漏任何一个真正解,也要避免有重复)
  • 确定枚举条件:是否是真正解的条件?
  • 枚举算法优化:可能解的范围是否还可以再缩小?(使可能解的范围降至最小,以便提高解决问题的效率)
  1. 实例:百钱买百鸡问题、填写运算符
  2. 使用场景:如果题目的规模不是很大,在规定的时间与空间限制内能够求解,那么最好是采用枚举法,而不用太在意是否还有更快的算法,这样可以有更多的时间去解答其他那题。

递推算法思想

  1. 说明:通过已知某个条件,利用特定的关系得出中间推论,然后逐步递推,直到得到结果为止。
  2. 算法思想
    1)顺推法:从已知条件出发,逐步推算出要解决问题的方法。
    2)逆推法:从已知结果出发,用迭代表达式逐步推算出问题开始的条件。
  3. 实例
    1)顺推:斐波那契数列/兔子数列(以兔子繁殖为例子引入)。
    2)逆推:猴子吃桃问题、母亲为儿子存4年学费

递归算法思想

  1. 算法思想:直接或间接调用自身。递归算法实际上是把问题转换为规模缩小了的同类问题的子问题。
  2. 注意
    1)递归是在函数或过程中调用自身的过程。
    2)在使用递归策略时,必须有一个明确的递归结束条件,被称为递归出口
    3)递归算法通常显得很简洁,但是运行效率较低,所以一般不提倡用递归算法设计程序。
    4)在递归调用过程中,系统用来存储每一层的返回点和局部变量。如果递归次数过多,则容易造成栈溢出,所以一般不提倡用递归算法设计程序。
  3. 实例:汉诺塔问题、阶乘问题。

分治算法思想

  1. 算法思想:把一个复杂的问题成两个或更多的相同或相似的子问题,再把子问题分成更小的子问题,直到可以直接求解的程度,最后将子问题的解进行合并得到原问题的解。
  2. 算法实现
    1)分解:将要解决的问题划分成若干个规模较小的同类问题。
    2)求解:当子问题划分的足够小时,用较简单的方法解决。
    3)合并:按原问题的要求,将子问题的解逐层合并,得到原问题的解。
  3. 实例:找出假币、大数相乘、世界杯比赛日程安排。
  4. 使用场景:如果问题具有以下四个特征,则一般可以选择使用分治法——
  • 问题的规模缩小到一定程度后可以很容易解决。(绝大多数问题都可以满足此特征,因为问题的复杂性一般是随着问题规模的增加而增加)
  • 问题可以分解为若干个规模较小的相同问题,即该问题具有最优子结构性质。(大多数问题都可满足,它反映了递归思想的应用)
  • 分解出的子问题的解可以合并为该问题的解。(此乃关键特征。如果具备前两条特征,但不具备本特征,可以考虑贪婪法或动态规划法)
  • 分解出的各个子问题是相互独立的,即子问题之间不包含公共的子问题。(此特征涉及到分治法的效率:如果各个子问题不相互独立,会重复的解公共的子问题。此时,可以使用动态规划法)

贪心算法思想

  1. 算法思想:在对问题求解时,总是做出在当前看来最好的选择,不追求最优解,而是快速找到满意解。
  2. 算法缺陷
  • 不能保证最后的解是最优的;
  • 不能用来求最大或最小解问题;
  • 只能求满足某些约束条件的可行解的范围。
  1. 算法实现
  • 贪心算法的基本思路——
    1) 建立数学模型来描述问题;
    2)把求解的问题分成若干子问题;
    3)对每一子问题求解,得到子问题的局部最优解;
    4)把子问题的局部最优解合成原来问题的一个解。
  • 实现算法的过程——
    1)从问题的某一初始解出发;
    2)while能向给定总目标前进一步;
    3)求出可行解的一个解元素;
    4)由所有解元素组合成问题的一个可行解。
  1. 实例:装箱问题、找零方案

试探法算法思想

  1. 说明:试探法也叫回溯法。该算法会暂时放弃关于问题规模大小的限制,并将问题的候选解按某种顺序逐一枚举和检验:
    1)当前候选解不可能是正确解时,就选择下一个候选解继续进行检验;
    2)当前候选解除了不满足问题规模的限制外,其他所有要求都满足时,就继续扩大当前候选解的规模,并继续试探下一候选解;
    3)当前候选解满足包括问题规模在内的所有要求时,该候选解就是问题的一个解。
  2. 算法思想:在试探法中,一旦发现原来的选择的假设情况是错误的,就放弃当前候选解,退回一步重新选择,继续向前试探,寻找下一个候选解,这一过程称为回溯。扩大当前候选解的规模,以继续试探的过程称为向前试探。
  3. 算法实现
  • 针对所给问题,定义问题的解空间;
  • 确定易于搜索的解空间结构;
  • 以深度优先方式搜索解空间,并在搜索过程中用剪枝函数避免无效搜索。
  1. 实例:八皇后问题、彩票组合。
  2. 其他
    在这里插入图片描述

动态规划(DP)算法

  1. 说明
  • 在现实项目中,会经常遇到不能简单地分解成几个子问题的复杂问题,而是需要分解出一系列的子问题。如果再采用把大问题分解成子问题,并综合子问题的解导出大问题的解的方法,会使得求解过程比较耗时,并且会按问题规模的增大而呈幂数级增加(这即递归)。为此产生了动态规划算法来解决此类问题。
  • 通常的做法是,为了节约重复求相同子问题的时间,引入一个数组,不管它们是否对最终解有用,把所有子问题的解存于该数组中,这就是动态规划法所采用的基本方法。【动态规划特性之重复子问题
  • 动态规划法就是分多阶段进行决策。多阶段决策过程是指把研究问题分成若干个阶段,由每个阶段都作出最优决策,从而使最终结果达到最优化。【动态规划特性之最优子结构
  • 动态规划实际上遵循了一套固定的流程:递归的暴力解法 —> 带备忘录的递归解法 —> 非递归的动态规划解法(这个过程是层层递进的解决问题的过程,如果没有前面的铺垫,直接看最终的非递归动态规划解法,就会觉得可望而不可即,甚至望而却步)。(这句话引自:https://leetcode-cn.com/problems/fibonacci-number/solution/dong-tai-gui-hua-tao-lu-xiang-jie-by-labuladong/)【于是,我按照这一流程进行了学习。点击这里。】
  1. 算法思路:根据时空的特点,将复杂的问题划分为相互联系的若干阶段,在选定系统行进方向之后,逆向从终点向始点计算,逐次对每个阶段寻找某种决策,使整个过程达到最优,所以又称为逆序决策过程。
  2. 算法实现
    1)划分阶段——按照问题的时间或空间特征,把问题分为若干个阶段。在此需要注意,这若干个阶段一定要是有序的或者是可排序的(即无后向型),否则问题就无法用动态规划求解。
    2)选择状态——将问题发展到各个阶段时所处于的各种客观情况用不同的状态表示出来。当然,状态的选择要满足无后向型。
  3. 实例:求两字符序列的最长公共字符子序列、斐波那契数列

迭代算法思想

  1. 算法思想:迭代法也称辗转法,是一种不断用变量的旧值递推新值的过程,与迭代法相对应的是直接法(或者成为一次解法),即一次性解决问题。迭代法又分为精确迭代和近似迭代,“二分法”和“牛顿迭代法”属于近似迭代法,功能比较类似。
  2. 说明:使用迭代算法时,需要做好以下三个方面的工作——
    1)确定迭代变量:在可以使用迭代算法解决的问题中,至少存在一个迭代变量,即直接或间接地不断由旧值递推出新值的变量。
    2)建立迭代关系式:迭代关系式是指如何从变量的前一个值推出其下一个值的公式或关系。通常可以使用递推或倒推的方式来建立迭代关系式,迭代关系式的建立是解决迭代问题的关键。
    3)对迭代过程进行控制:在编写迭代程序时,必须确定在什么时候结束迭代过程,不能让迭代过程无休止地重复执行下去。通常可分为如下两种情况来控制迭代过程:
  • 所需的迭代次数是确定的、可以计算出来的,则构建一个固定次数的循环来实现对迭代过程的控制;
  • 所需的迭代次数无法确定,则需要进一步分析出用来结束迭代过程的条件。
  1. 实例:求平方根。

模拟算法思想

  1. 说明:模拟算法是最基本的算法,是对程序员基本编程能力的一种考察,其解决方法就是根据题目给出的规则对题目要求的相关过程进行编程模拟。在解决模拟类问题时,需要注意字符串处理、特殊情况处理、全面考虑所有情况和对题目意思的理解等方面。
    在程序设计中,可使用随机函数来模拟自然界中发生的不可预测情况,如使用srand()、rand()函数(头文件time.h中)生成随机数,其中srand()用于初始化随机数发生器,然后使用rand()来生成随机数。
    实例:猜数字游戏、掷骰子游戏
  • 3
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值