什么是算法
- 《算法导论》:定义良好的计算过程,它取一个或一组值作为输入,并产生一个或一组值作为输出。
- 《计算机程序设计艺术》:从一个步骤开始,按照既定的顺序执行完所有的步骤,最终结束(得到结果)
-《数据结构与算法分析》:一系列的计算步骤,将输入数据转换成输出的结果。 - Knuth 总结了算法的四大特征
- 确定性:算法的每个步骤都是明确的,对结果的预期也是确定的
- 有穷性:算法步骤数量有限
- 可行性:算法每一步都可以执行
- 输入和输出:有输入和输出
算法设计基础
程序基本结构:
顺序执行、循环执行、分支跳转
基本数据结构(线性表):
- 数组:(定长、可变)查找、遍历
- 链表:插入、删除
- 栈:LIFO,只能表的一端插入和删除数组元素
- 队列:(单,双向、环形、双端)FIFO
复杂数据结构(非线性):
- 树:层次数据(N叉树 { 连通无环图、B树、字典树、堆, 二叉树 [二叉查找树 (线段树),红黑树(区间树),AVL树 ] })
- B树:多路分支且有序的层次结构
- 区间树:区间查询相关的问题,比如判断区间之间是否存在重叠区域等
- 线段树:需要查询在一个大的区间上,某个小范围内的统计值
- 字典树:利用字符串的公共前缀或后缀,节约存储空间;匹配前缀或后缀
- 堆:排序
- 集合:
- 一部分是对集合元素的操作,包括插入和删除集合元素、判断一个元素是否属于集合等;
- 另一部分是集合之间的关系运算,包括集合的求交集、并集运算以及求差运算等
- hash:查找
- 图:邻接矩阵(二维数组方式)或邻接表(链表或可变长数组)
- 有向/无向,连通/非连通,带权/不带权
- 深度优先/广度优先搜索
算法设计常用思想
算法设计范式
贪婪法、分治法和递归法、动态规划法、线性规划法、搜索和枚举(包括穷尽枚举)
贪婪法
定义子问题,求解局部最优解
分治法和递归法
拆解问题为小问题,将子问题解决的结果合并
动态规划法
定义最优子问题、定义状态、定义转移方程
优化技巧:状态标志参数、备忘录
枚举
解空间的穷举搜索
优化技巧:
- 启发式:利用搜索过程的信息跳过一些状态
- 剪枝:已经不可能是最优解的节点不再递归;已经处理过的节点不再重复处理
- 收敛:定义递归最大深度
例子
三个水桶等分 8 升水
问题描述
3,5,8升三个水桶,分8升水桶的8升水为2份4升水
方法:穷举 + 剪枝优化
type Status [3]int // 3,5,8升三个水桶
type Action [3]int // from, to, amount
type HasStatus map[Status]int // 经过的State,是第几步执行到的
type StatusAt map[int]Status // 第n步结束时的State
妖怪与和尚过河
有三个和尚和三个妖怪过河,船可载2人,无论是在河的两岸还是在船上,只要妖怪的数量大于和尚的数量,妖怪们就会将和尚吃掉。求安全过河方案
方法:穷举 + 剪枝优化
稳定匹配与舞伴问题
爱因斯坦的思考题
groups[2].itemValue[type_drink]=drink_milk
项目管理与图的拓扑排序
利用 AOV 网表示有向图,可以对活动进行拓扑排序,根据排序结果对工程中活动的先后顺
序做出安排。但是寻找关键路径,估算工程活动的结束时间,则需要使用 AOE 网表示有向图
RLE压缩算法与PCX图像
大整数运算
加、减、乘、除、幂
RSA加密解密
常用技巧
数组下标处理
控制好数组越界问题,可以简化数据模型,优化代码结构
var ChineseInt [10]string{ "零", "一", "二", "三”", "四", "五", "六", "七", "八", "九"}
ChineseInt[5] = "五" // 数字转汉字
一重循环实现两重循环的功能
用一重循环遍历二维表
// 一维=>二维
int row = i / M
int col = i % N
// 二维=>一维
int i = row * N + col
棋盘(迷宫)类算法方向遍历
表驱动 > switch-case > if-else
type IdxOffset struct {
int row
int col
}
dirOffset = [4]IdxOffset{{0,-1},{-1,0},{0,1},{1,0}}; // 可扩展到斜对角等
for _, offset := range dirOffset {
curRow = x + offset.row
curCol = y + offset.col
}
代码的一致性处理技巧
数据操作的边界是最容易出错的地方, 因为边界数据的处理往往和内部数据的处理不太一样.
解决办法,就是尽量保持一致,减少if-else判断。上面表驱动也是一致性处理的案例
tail = (tail + 1) % N
链表和数组的配合使用
可以利用数组定义一次性分配好链表需要的内存
var blocks = make([]Block, 0, 64) // 8x8 黑白棋,64个格子
“以空间换时间”
比如动态规划的备忘录方法、剪枝的经过状态记录