前面几篇文章大概介绍了几个经常使用的数据结构。算法
根据个人理解,数据结构帮助咱们对须要解决的问题进行描述,而算法就是咱们解决问题方案的具体描述。它包括对问题的分析及研究(创建描述问题的数学模型),而后根据一些策略和思想制定出解决问题的方案。bash
这篇文章讲述了四个算法设计时的经常使用思想并给出了相应的例子:数据结构
这个名字是来自于《算法的乐趣》,其实就是穷举法。这里的解空间是全部可能的解的集合。加上解空间就是为了说明:穷举是在可能的解的集合中查找的,并非漫步目的的乱找。app
步骤:函数
穷举法能够说是解决不少问题的 “通用算法” 了,可是穷举法最大的问题就是问题的规模。因此,咱们须要一些策略来进行咱们的穷举。优化
策略:ui
举例: 在一个笼子里关着若干只鸡和若干只兔,从上面数共有35个头;从下面数共有94只脚。问笼中鸡和兔的数量各是多少?spa
// head = 35, foot = 94
func getNumberBy(head: Int, foot: Int) -> (Int, Int)? {
var numOfRabbit: Int = 1
var numOfChicken: Int = head - numOfRabbit
while (numOfRabbit*4 + numOfChicken*2) != foot {
numOfRabbit += 1
numOfChicken = head - numOfRabbit
if numOfRabbit > head {
return nil
}
}
return (numOfRabbit, numOfChicken)
}
复制代码
又称贪心算法(greedy algorithm),是寻找最优解问题的经常使用方法。这种方法模式通常将求解过程分为若干个步骤,在每一个步骤都应用贪心原则,选取当前状态下最好的或最优的选择(局部最有利的选择),并以此但愿最后堆叠的结果也是最好或最优的解。设计
大多数状况下,因为贪婪法在选择策略上的“短视”,会错过真正的最优解,可是贪婪法简单高效,省去了为了寻找最优解可能须要的穷举操做,能够获得与最优解比较接近的近似最优解。code
基本思想:
举例: 如今咱们有一个背包,里面能够装下 150 单位重量的物体,如今咱们有一系列重量的东西,怎么样的组合让背包装下最多的东西?
let weight = [35, 30, 60, 50, 40, 10, 25] // 重量
// total = 150
func greedy(total: Int) -> [Int] {
var result: [Int] = []
let tempArr = weight.sorted()
var temp = 0
for index in 0..<weight.count {
temp = temp + tempArr[index]
if temp < total {
result.append(tempArr[index])
}
}
return result
}
复制代码
分治法的设计思想是将没法着手解决的大问题分解成一系列规模较小的相同问题。而后逐个解决小问题,即分而治之。分治法产生的子问题与原始问题相同,只是规模减少,反复使用分治方法,可使得子问题的规模不断减少,直到可以被直接求解为止。
基本思想:
举例: 一我的在 1~100 中随机选取一个数,如何才能以最少的次数猜到这个数字?每次猜想后,都会得知猜想结果小了、大了或正确。
// 一个典型的二分搜索
// source = [1, 2, 3, ... 100], target 为须要猜想的数字
func binarySearch(source: [Int], target: Int) -> Int? {
guard source.count != 0 else {
return nil
}
var low: Int = 0
var high: Int = source.count - 1
var mid: Int = 0
var count = 0
while low <= high {
count += 1;
mid = low + (high - low) / 2
if target < source[mid] {
high = mid - 1
} else if target > source[mid] {
low = mid + 1
} else {
return mid
}
}
return nil
}
复制代码
解决多阶段决策问题经常使用的最优化理论。原理是把多阶段决策过程转化为一系列的单阶段决策问题,利用各个阶段之间的递推关系,逐个肯定每一个阶段的最优化决策,最终堆叠出多阶段决策的最优化决策结果。
须要知足的条件:
最优化原理:
无论以前决策是不是最优决策,都必须保证从如今开始决策是在以前决策基础上的最优决策。
无后向性:
当各个阶段的子问题肯定之后,对于某个特定阶段的子问题来讲,它以前的各个阶段的子问题的决策只影响该阶段的决策,对该阶段以后的决策不产生影响。也就是说,每一个阶段的决策仅受以前决策的影响,可是不影响以后各阶段的决策。
有重叠子问题:
即子问题之间是不独立的,一个子问题在下一阶段决策中可能被屡次使用到。(该性质并非动态规划适用的必要条件,可是若是没有这条性质,动态规划算法同其余算法相比就不具有优点)
动态规划算法与分治法最大的差异是:适合于用动态规划法求解的问题,经分解后获得的子问题每每不是互相独立的(即下一个子阶段的求解是创建在上一个子阶段的解的基础上,进行进一步的求解)。
基本思想:
定义最优子问题:
肯定问题的优化目标以及决策最优解,并对决策过程划分阶段。
定义状态:
对起始状态施加决策,使得状态发生改变,获得决策的结果状态。状态的定义是创建在子问题定义的基础上的,所以状态必须知足 “无后效性”。
定义决策和状态转换方程:
决策就是能使状态发生转变的选择动做。状态转换方程是根据上一阶段的状态和决策来导出本阶段的状态的方程。
肯定边界条件:
边界条件其实就是状态转移方程的终止条件。
举例:
有n级台阶,一我的每次上一级或者两级,问有多少种走完n级台阶的方法?
分析:
动态规划的实现的关键在于能不能准确合理的用动态规划表来抽象出实际问题。在这个问题上,咱们让f(n)表示走上n级台阶的方法数。
那么当 n 为 1 时,f(n) = 1, n 为 2 时,f(n) = 2, 就是说当台阶只有一级的时候,方法数是一种,台阶有两级的时候,方法数为 2。那么当咱们要走上 n 级台阶,必然是从 n-1 级台阶迈一步或者是从 n-2 级台阶迈两步,因此到达 n 级台阶的方法数必然是到达 n-1 级台阶的方法数加上到达 n-2 级台阶的方法数之和。即 f(n) = f(n-1) + f(n-2)。
// n 为须要走的台阶总数
func calculateStep(n: Int) -> Int {
//若是为第一级台阶或者第二级台阶 则直接返回n
if n < 1 {
return 0
}
if n == 1 || n == 2 {
return n
}
var a = 1
var b = 2
var temp = 0
for _ in 3..<n+1 {
temp = a + b
a = b
b = temp
}
return temp
}
复制代码