什么是算法?
人生皆算法,算法的本质,是解决问题的方法,遇到问题,寻找答案,解决问题,是作为一个人,一生都在做的事情。
算法是人类思维的产物,是解决问题的方案,并且,它能够映射到计算机世界去实现,完成一些人类不擅长的事情,比如大量重复的计算。
算法很有魅力,它很特别,但是并不神秘,我们时时刻刻都在运用着算法背后的“解决问题”的思想去生活,去过着每一天。
用计算机思维去思考问题
算法是普通的,也是特别的,它是人类使用计算机思维思考的产物,能够让人类更加充分地利用计算机这个人类发明的工具,这也是信息时代特有的产物。
既然是计算机思维,当然有其特点:
概念不重要,理解即可,不必记忆,不必急于掌握,直接看实例体会。
“玩具”问题
计数:判断一个byte(无符号整数)里面有多少个bit的值是1
算法一
很简单的想法,将十进制数转换为一位一位的二进制,然后看看是不是1就好了,就是简单的数数。
// method 1
int cal_1(unsigned char number) {unsigned char count = 0;while (number != 0){int div = number % 2;if (div == 1) {count++;}number /= 2;}return count;
}
算法二
上面是我们解决问题的第一种想法。
下面我们加一种假设,假如内存空间足够大,且使用函数次数足够多,我们是不是可以提高算法的效率?
要知道,1字节大小的无符号整数,一共28 = 256种,如果我们连续10000次调用函数,每一次都使用除二取余法(算法1),就大量重复计算了,这个时候不妨提前算好把结果存起来,然后直接访问结果(就像缓存、cache那样的思想)。
也就是所谓的打表查表
// 计数:判断一个byte(无符号整数)里面有多少个bit的值是1
#include <iostream>
using namespace std;int num_table[256] = { 0 };// method 1
int cal_1(unsigned char number) {unsigned char count = 0;while (number != 0){int div = number % 2;if (div == 1) {count++;}number /= 2;}return count;
}// method 2
int cal_2(unsigned char number) {return num_table[number];
}int main(){// 存储答案for (unsigned char i = 0; i < 255; i++) {num_table[(int)i] = cal_1(i);}// 直接访问cout << cal_2(15);return 0;
}
我们开始使用算法1,存储了一张表,之后再需要使用的时候,就能够直接查表得结果,而不需要再重复计算了,如果调用100000万次,效率将会比算法1显著提高。
当然……由于数据量对计算机来说太小了还是,测试不出很大的差异其实。
如果规模更大,会有显著效果。
算法三
对于算法一
- 省空间,因为没占用多少数据段
对于算法二 - 省时间,因为提前储存好了答案,只需要访问数组就可以
对于前面两种算法,是根据不同需求不同情况取使用的,但是涉及到了算法设计考虑的两个维度:时间和空间。
算法一省空间,费时间,算法二省时间废空间,那么能不能兼得?
其实这种
不走极端取中间的兼得思想
在计算机中经常需要用到。
我们需要既省时间又省空间的算法。
与cache类似的设计思想
我们可以使用哈希表,采取如下策略
- 如果表中有值,那就直接取
- 如果没有,就去计算,并且存表中
这样一来,做到了拿走需要的,保存需要的,同时考虑了时间和空间两方面的因素。
我们使用unordered_map
完成这件事:
// 计数:判断一个byte(无符号整数)里面有多少个bit的值是1
#include <iostream>
#include <unordered_map>
using namespace std;typedef std::unordered_map<unsigned char, int> result_map;// method three
int cal_3(unsigned char number, result_map &map) {result_map::iterator count = map.find(number);if (count != map.end()) // 在无序映射表中{cout << "在表中 ";return count->second;}else // 不在表中{cout << "不在表中 ";int result = cal_1(number);map.insert(result_map::value_type(number,result)); // 插入键值对return result;}}int main(){result_map map;cout << cal_3(15, map) << endl; // 不在表中 4cout << cal_3(15, map) << endl; // 在表中 4cout << cal_3(10, map) << endl; // 不在表中 2cout << cal_3(10, map) << endl; // 在表中 2return 0;
}
这样,通过无序映射表,我们就完成了既节省时间,又节省空间的目的。
小结
我们来回顾一下。
对于这个问题
- 我们看到了问题,理解了问题
- 拆解问题,变成自己熟悉的
- 解决问题,创建解决问题的过程方案
- 设计解决流程,将其代码实现
- 根据实际情况,优化解决方案
- 综合考虑算法的时间和空间两个维度的指标
我们通过这个超级简单的例子,初步了解了算法的基本情况,我们继续往下进行。
连续子序列和
让我们体会一下不同的算法,不同的解决方案,感受一下它们之间的时间和空间上的性能差异。
一件事情,往往都是很多种解决方案,并且都能到达彼岸,但是过程不一样,走过的路不同,花的时间也不同,算法的时空性能也是一样的。
充分体会: 计算机世界是人类世界的二进制映射,与人类生活息息相关。
算法1:三层循环
- 取某个首位置
i
- 取
i
后的某个位置j
- 计算
[i,j]
数值的和,与最大值比较,更新最大值
这是一种最朴素的思想了,我们看图。
因此,我们需要
for(int i = 0; i < char_size; i++){for(int j = i; j < char_size; j++){1. 求和2. 更新maxValue}
}
代码如下:
#include <iostream>
#include <vector>
using namespace std;int max_subsequence_sum(const vector<int> &a) {int max_sum = 0;for (int i = 0; i < a.size(); i++) {for (int j = i; j < a.size(); j++) {int current_sum = 0;for (int k = i; k <= j; k++)current_sum += a[k];if (current_sum > max_sum)max_sum = current_sum;}}return max_sum;
}int main()
{vector<int> a = { 1,3,-1,2 };cout << max_subsequence_sum(a);return 0;
}
但是,毫无疑问,三重循环,时间消耗过多了,我们得看看能不能优化。
将三重循环转换为递归。
代码暂不写
算法2:两层循环
算法3:一层循环
算法4:递归
算法比较
小结
之所以没有谈及后面的代码,因为对于初学者来说比较复杂,看看就行了,充分体会的是同一个问题,达成同一个目的的情况下,不同算法的差异巨大,这也是算法魅力所在。
致谢
感谢北交大算法MOOC!