学习目标:
学习《算法图解》这本书,记录学习到的内容
学习内容:
这本书将算法像小说一样解释,简单易懂。主要涉及算法有:
- 二分查找
- 选择排序
- 快速排序
- 广度优先搜索
- 狄克斯特拉算法
- 贪婪算法
- 动态规划
- K最近邻算法
章节总结
第一章:算法介绍
- 该章主要介绍了二分查找和大O表示法。算法对性能的影响
- 程序 = 算法 + 数据结构
二分查找算法
- 需要有序数组
- 时间:O(log(n)) 此处log = log2()
- 空间:O(1)
public int binary_serach(List<int> list, int item) {
int low = 0;
int high = item.Count - 1;
while (low <= high) {
int mid = (high + low) / 2;
int guess = list[mid];
if (guess == item) {
return mid; // 找到了
}
if (guess > item) {
high = mid - 1;
} else {
low = mid + 1;
}
}
return -1; // 没有找到目标
}
第二章:选择排序
- 介绍了链表、数组、选择排序
- 数组读取速度很快,插入删除很慢
- 链表插入删除很快,读取速度比数组慢
选择排序
- 时间:O(n^2)
- 空间:O(n)
public int selectionSort(List<int> list) {
List<int> newList = new();
for (int i = 0; i < list.Count; i++) {
int idx = findSmallest(list);
newList.add(list[idx]);
list.remove(idx);
}
return newList;
}
private int findSmallest(List<int> list) {
int smallestIdx = 0;
for (int i = 1; i < list.Count; i++) {
if (list[smallestIdx] < list[i]) {
smallestIdx = i;
}
}
return smallestIdx;
}
第三章:递归
- 讲了递归和栈,可以用栈的方式存储递归的数据,达到用单纯的循环来实现递归方法
- 计算机在内部使用被称为调用栈的栈
- 每个递归函数都有两个条件:基线条件和递归条件
第四章:快速排序
- 分而治之(divide and conquer, D&C) 一种递归式问题解决方式
- 分而治之并非可解决问题的算法,而是一种解决问题的思路
- 两个步骤:
- 找到基线条件,该条件需尽可能简单
- 缩小规模,直至满足基线条件
- 时间:O(nlog(n))
- 空间:O(log(n))
void QuickSort(int[] arr, int left, int right) {
int pivot = arr[(left + right) / 2];
int i = left, j = right;
while (i <= j) {
// 分区操作
while (arr[i] < pivot) i++;
while (arr[j] > pivot) j--;
if (i <= j) {
// 交换元素
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
i++;
j--;
}
}
// 递归排序
if (left < j) QuickSort(arr, left, j);
if (i < right) QuickSort(arr, i, right);
}
第五章:散列表
- 字典结构
- 填装因子:散列表包含的元素数/位置总数
- 散列函数:隐射key到value(目标数组所存的位置)
第六章:广度优先搜索
- 一种基于图的查找算法
- 广度有限搜索适用于无权重的图,求最短路径
- 适用问题:
- 从节点A出发,有前往B的路径吗?
- 从节点A出发,前往B的哪条路径最短?
/**
* 使用队列实现 bfs
* @param root
*/
private static void bfs(Node root) {
if (root == null) {
return;
}
Queue<Node> stack = new LinkedList<>();
stack.add(root);
while (!stack.isEmpty()) {
Node node = stack.poll();
System.out.println("value = " + node.value);
Node left = node.left;
if (left != null) {
stack.add(left);
}
Node right = node.right;
if (right != null) {
stack.add(right);
}
}
}
第七章:狄克斯特拉算法
- 适用于非负加权图,找出最短路径
- 如果包含负权重,需要用贝尔曼-福德算法
public void DijkstraSearch(costs) {
node = find_lowest_cost_node(costs);
while node is not None:
cost = costs[node]
neighbors = graph[node]
for n in neighbors.keys():
new_cost = cost + neighbors[n]
if costs[n] > new_cost:
costs[n] = new_cost
parents[n] = node
processed.append(node)
node = find_lowest_cost_node(costs)
}
第八章:贪婪算法
- 贪婪算法给我的感觉就是求局部最优解
- 不求最佳,追求近似最佳
- 适用于处理没有快速算法的问题,几乎不可能解决的问题?
- 这本书给了个例子:安排课程,每个课程有一定的时间,希望尽可能多的课程在同一个教室上
贪婪算法的逻辑:找出时长最短的课程,上完后找市场第二短的课程。重复这样就能找到答案
-每步都采取局部最优解,最终达成全局最优解(信不信由你 - NP完全问题:旅行商问、集合覆盖问题。这些都要求我们计算出所有的解,没有快速解决的算法。用近似求解就算不错了。
- 找出满足条件最多的目标
- 重复这个步骤直到满足所有条件
-如何判断问题是NP完全问题: - 元素较少时算法运行非常快,但元素数量的增加,速度变得非常慢
- 涉及”所有组合“的通常是NP完全问题
- 不能将问题分成小问题,必须考虑各种可能的情况
- 问题涉及序列且难以解决
- 问题涉及集合且难以解决
第九章:动态规划
- 动态规划是一种思想,它没有公式,它给予的是一种使用网格能解决特定问题的想法
- 通常解决于能将问题拆成离散子问题的
- 每个单元格是一个子问题,我需要考虑的就是如何解决子问题
- 问题案例:
- 有限重量的背包偷东西,每个东西重量价值各不相同
- 字符串相似度比较,有关最长公共子串(字符串里最长的连续对应位置相同的字符)和最长公共子序列(字符串里对应位置相同的字符数)
|-|H|I|S|H|
|F|0|0|0|0|
|I|0|1|0|0|
|S|0|0|2|0|
|H|0|0|0|3|
第十章:K最近邻算法(KNN算法)
- 适用于推荐算法,或者根据以往的特征,预测结果(回归)
- 能否挑选合适的特征是KNN算法的成败
- 创建推荐系统
- 特征抽取 (选择合适的特征,转换为可比较的数字)
- 回归 即预测结果
- 适用案例
- 判断水果是橘子还是柚子
- Netflix推荐用户电影
- 一般如果有N个用户,需要比较的邻居数为sqrt(N)
- 计算相似程度,可以用距离公式,也可用余弦相似度。
- 对于两个不同用户,它们品味相似,但是有的打分给5分,有的通常只给3分
- 如果用距离公式就不好比较,除非先进行归一化
- 余弦相似度不计算两个矢量的距离,而比较它们的角度,更适合此类问题