文章目录
二分法
参照二分查找。
递归
递归通俗来讲就是不断调用自身。
递归只是让解决方案更加清晰,并没有性能上的优势。
如果使用循环,程序的性能可能更高;如果使用递归,程序可能更容易理解。
递归有三个基本组成部分,分别是执行的内容(可省略)、基线条件(函数不再调用自己)以及递归条件(函数调用自己)。
int factorial(int x){
	// 基线条件
	if (x == 1)
		return x;
	// 递归条件
	else
		return x * fab(x);
}
 
递归在使用过程中可能占用大量的内存(调用栈),此时有两种解决方案:
- 重新编写代码,转而使用循环;
 - 使用尾递归。
 
分而治之(D&C,divide and conquer)
分而治之策略是递归的,包括了两个过程:
- 找到基线条件,这种条件必须尽可能简单;
 - 不断将问题分解(或者说缩小规模),直到符合基线条件。
 
分而治之是基于递归的,更是一种解决问题的思想,递归偏于解决问题的策略/手段。
以对数组中的数求和为例:
首先,我们需要找到基线条件:数组为空时返回0或者数组只有一个元素时返回该元素;
接着,我们需要对问题进行分解,即每一次递归都将数组的最后一个元素+sum(剩下的元素构成的数组)。因为,传递给sum的数组变得简单了,这也是一种问题规模的缩减。
int sum(vector<int> seq){
	if seq.empty()
		return 0;
	else if (seq.size == 1)
		element = seq.back();
		seq.push_back();
		return element + sum(seq);
}
 
贪婪算法
贪婪算法的本质:每步都选择局部最优解,最终得到的就是全局最优解。
并非在任何情况下都行之有效。
背包问题
有一个小偷,只偷最贵的东西,但是他的包只能装下35磅的物品。
此时,在他眼里,有三样物品:
| 物品 | 重量/kg | 价格/¥ | 
|---|---|---|
| 音响 | 30 | 3000 | 
| 笔记本电脑 | 20 | 2000 | 
| 吉他 | 15 | 1500 | 
显而易见,按照贪婪算法的要求,小偷无法使自己的利益最大化,即得不到最优解。
集合覆盖问题
问题是:一共有八个州,四个电台,希望能选择尽可能少的电台将这八个周覆盖了。
4个电台就有 2 4 2^4 24个集合需要考虑,那么枚举的话,时间为 O ( 2 n ) O(2^n) O(2n)。
为了加快解题速度,解题思路是:每次都选择覆盖多的。
这是一种近似算法,得到近似解。
#include <iostream>
#include <map>
#include <string>
#include <set>
#include <algorithm>
#include <vector>
using namespace std;
int main() {
	// 电台信息
	map<string,vector<string> > stations;
	stations["kone"] = { "id", "nv", "ut" };
	stations["ktwo"] = { "wa", "id", "mt" };
	stations["kthree"] = { "or", "nv", "ca" };
	stations["kfour"] = { "nv", "ut" };
	stations["kfive"] = { "ca", "az" };
	// 初始化
		// 当前未覆盖的州
	vector<string> states_needed = { "mt", "wa", "or", "id", "nv", "ut", "ca", "az"};
		// 候选电台列表
	vector<string> final_stations;
		// 选中电台
	string best_station;
		// 选中电台所覆盖的州
	vector<string> states_covered;
	// 贪心算法
	while (!states_needed.empty()) {
		// 遍历电台,找最优
		for (auto& s : stations) {
			// 候选电台与未覆盖州的交集
			vector<string> covered;
			sort(states_needed.begin(), states_needed.end());
			sort(s.second.begin(), s.second.end());
			set_intersection(states_needed.begin(), states_needed.end(), s.second.begin(), s.second.end(), back_inserter(covered));
			if (covered.size() > states_covered.size()) {
				best_station = s.first;
				states_covered = covered;
			}
		}
		// 记录选中电台
		final_stations.push_back(best_station);
		// 删除选中电台
		stations.erase(best_station);
		// 删除已经覆盖的州
		set<string> states_needed_set(states_needed.begin(), states_needed.end());
		for (auto& s : states_covered)
			states_needed_set.erase(s);
		states_needed.assign(states_needed_set.begin(), states_needed_set.end());
		// 重置
		best_station = {};
		states_covered = {};
	}
	for (auto& s : final_stations)
		cout << s << endl;
	return 0;
}
 
在写代码的过程中,有以下几个注意点:
- 一些参与比较,时常更新的值要注意是否每个循环都要初始化;
 - 使用交集算法
set_intersection时,需要注意的有:将两个集合排序;通过push_back插入结果,不是赋值;选取支持push_back的容器。 
NP完全问题
没有办法判断问题是不是NP完全问题(没有快速算法的问题),但是是有一些蛛丝马迹可循的:
- 元素较少时算法的运行速度非常快,但随着元素数量的增加,速度会变的非常慢;
 - 涉及“所有组合”的问题通常是NP完全问题;
 - 不能将问题分成小问题,必须考虑各种可能的情况。这可能是NP完全问题;
 - 如果问题可转化为集合覆盖问题或旅行商问题,则肯定是NP完全问题。
 
NP完全问题可以通过近似算法得到近似解。
动态规划
再探背包问题
动态规划问题和NP完全问题的目的都是为了找一个最优解,但区别在于,动态规划问题看似是个NP问题,但是其可以分解成小问题。
比如上文我们描述的背包问题。
我们可以先解决子背包问题,再逐步解决原来的问题。
我们设背包承重4磅。吉他1磅,1500美金;音响4磅,3000美金;笔记本电脑3磅,2000美金。
我们首先给出一个网格:
| 1 | 2 | 3 | 4 | |
|---|---|---|---|---|
| 吉他 | ||||
| 音响 | ||||
| 笔记本电脑 | 
从左到右,表示逐步扩大的背包承重数;从上到下,表示可供选择的物品的增加。
每一格的含义是,在x的承重下,从y(该行及往上的物品)中偷盗物品可以获得的最大金额。
于是,这张表格可以写成:
| 1 | 2 | 3 | 4 | |
|---|---|---|---|---|
| 吉他 | 1500 | 1500 | 1500 | 1500 | 
| 音响 | 1500 | 1500 | 1500 | 3000 | 
| 笔记本电脑 | 1500 | 1500 | 2000 | 3500 | 
该表格的公式为(i表示行,j表示列):
     
      
       
        
         C
        
        
         E
        
        
         L
        
        
         L
        
        
         [
        
        
         i
        
        
         ]
        
        
         [
        
        
         j
        
        
         ]
        
        
         =
        
        
         max
        
        
         
        
        
         (
        
        
         C
        
        
         E
        
        
         L
        
        
         L
        
        
         [
        
        
         i
        
        
         −
        
        
         1
        
        
         ]
        
        
         [
        
        
         j
        
        
         ]
        
        
         (
        
        
         上
        
        
         一
        
        
         个
        
        
         单
        
        
         元
        
        
         格
        
        
         的
        
        
         值
        
        
         )
        
        
         ,
        
        
         当
        
        
         前
        
        
         商
        
        
         品
        
        
         的
        
        
         价
        
        
         值
        
        
         +
        
        
         C
        
        
         E
        
        
         L
        
        
         L
        
        
         [
        
        
         i
        
        
         −
        
        
         1
        
        
         ]
        
        
         [
        
        
         j
        
        
         −
        
        
         当
        
        
         前
        
        
         商
        
        
         品
        
        
         的
        
        
         重
        
        
         量
        
        
         ]
        
        
         )
        
       
       
        CELL[i][j]=\max(CELL[i-1][j](上一个单元格的值),当前商品的价值+CELL[i-1][j-当前商品的重量])
       
      
     CELL[i][j]=max(CELL[i−1][j](上一个单元格的值),当前商品的价值+CELL[i−1][j−当前商品的重量])
 这其实就是一个有约束条件的优化问题,约束条件为背包承重,优化对象为偷盗的钱财。
细节补充
- 如果再增加一个物品,不过是给这个表格再加一行之后套公式求解;
 - 如果补充了一件0.5磅的物品,那么考虑的粒度更细,我们网格也要更细;
 - 对于商品,只能一整件,不能说偷商品的百分之多少;
 - 每个子问题都是离散的,不存在依赖关系。
 
设计动态规划模型时的注意:
- 每种动态规划都涉及网络;
 - 单元格中的值通常是我要优化的值,
 - 每个单元格都是一个子问题,因此你应考虑如何将问题分成子问题。
 
计算编辑距离的过程也是动态规划的思路!










