来源于《算法图解》
1 二分查找
前提:给定数据都是有序排放的。
思想:从中间猜,每次都排除数据的一半。
比如:1,2,3....,100 数组. 依次猜的数为:50,75,88,94,97,99,100。
根据如下程序,最极端情况才需要猜测7次,那二分查找最大的步数是公式是什么呢?
step = log2n
int binary_search(int search)
{
int low =0;
int high = strlen(list) -1;
int middle = (low + high) / 2;
int guess = list[middle];
while(list[middle] != search)
{
if(search > list[middle])
low = middle + 1;
if(search < list[middle])
high = middle -1;
if(low == high)
break;
middle = (low + high) / 2;
}
return middle;
}
2 大O 表示法
大O表示法指出了算法有多快,大0表示法计算的是操作数,算法运算时间用大0表示,不是以秒为单位,是从增速的角度度量的。
常见的大O运行时间:
O(log n): 对数时间,比如二分查找法
O(n):线性时间,比如简单查找法(按照顺序依次查找)
O(n*log n):快排
O(n^2): 选择排序,一种较慢的排序方法。
0(n!):旅行商问题,非常慢的算法。
3 数组与链表
链表:每个元素都存储下一个元素的地址,链表的优势在于插入元素方面,不能直接读取某个元素需遍历。
数据:每个元素都存放在一块连续的存储空间,能直接读取某个元素,插入元素很多元素都要平移。
下图为在进行对数组与链表进行查找,插入,删除时其大0 运行时间。
链表数组:facebook 存储用户信息时既不是使用数组,也不时链表而是链表数组。
4 二分查找法
思想:遍历这个链表或数组,选择出最大值放到第一个,接着遍历剩余的元素,将第二大值放入第二个, 依次循环,直到最后一个。
int SelectMaxElement(char *p, int len)
{
int maxElementIndex = 0;
for(int i = 0; i < len -1;i++)
{
if(p[i] > maxElement)
MaxElementIndex = i;
}
return MaxElementIndex;
}
int SelectSort(char *p , int len)
{
int tempValue = 0 , index = 0;
for(int i = 0;i < len -1, i++)
{
index = SelectMaxElement(p+i,len-i);
tempValue = p[i];
p[i] = p[i+index];
p[i+index] = p[i];
}
}
5 递归
递归是指自己调用自己的函数,每个递归函数都有两部分:递归条件和基线条件。
递归条件:函数自己调用自己。
基线条件:函数不再自己调用自己,避免形成无限循环。
栈有两种操作,压入和弹出,所有函数调用都进入调用栈,调用栈可能很长,这将占用大量的内存。
6 快速排序
分而治之:当你遇到使用任何算法都无法解决的问题时,或许分而治之是一种通用的解决方法。分而治之算法(D&C)是递归的,使用D&C解决问题必须包含以下两个步骤:1 找出基线条件,2不断将问题分解(或者说缩小规模),直到符合基线条件。
快速排序:它比选择排序快得多,也使用的D&C 。
算法思想:找一基准值,将小于这基准值的放一边, 大于这基准值的放一边,这被称为分区。然后采用D&C ,缩小问题规模重复。
现在你有:一个由所有小于基准值的数字组成的子数组;基准值;一个由所有大于基准值的数组组成的子数组。
快排的大0表示法: 0(nlog n)
7 散列表
散列表:根据散列函数确定元素的存储位置,散列表由键和值组成。适用于:模拟映射关系,防止重复,缓存/记住数据。
散列函数:相同的输入返回的索引值是一样的, 不同的数据返回的索引值不同。
散列冲突:不同的输入被映射到了相同的位置,解决办法:在这个位置存储一个链表。
好的散列表,要避免冲突,需要有:较低的装填因子,良好的散列函数。
装填因子:散列表中多少位置是空的,装填因子适合控制在0.7,大于这个值就应该调整列表的长度。
装填因子计算:已填充的位置/ 列表长度。比如上图列表总长度为8,已填充位置为3,因此装填因子为3/8.
良好的散列函数:让值呈均匀分布,不要扎堆。
散列表的大0 时间:O(1),根据散列函数能快速定位到键值对,在执行效率上是非常高的。
8 广度优先搜索算法
最短路径问题:使用图来建立问题模型,使用广度优先搜索解决问题。
图:由节点和边组成,用于模拟不同东西是如何相连的。
广度优先搜索算法:假设你要找一个名叫Tom的人,首先你遍历自己的通讯录是否有个叫Tom的人,假设没有,那就必须在朋友的朋友中查找,已此循环,直到找到Tom.
查找最短路径: 首先从你自己通讯录找,这叫一度关系,然后在从朋友的通讯路找,这叫二度关系....。自己的朋友是一度关系,朋友的朋友是二度关系。 一度关系强于二度关系,二度关系强于三度关系,查找顺序为一度关系,到二度,三度。因此需要按顺序添加,有一个可实现这种目的的数据结构,那就是队列。
实现图:通过散列表,来表述你通讯录的关系。构造散列表来描述关系。
算法步骤:
1.创建一个队列,用于存储要检查的人。
2.从队列中弹出一个人
3.检查这个人是否为自己寻找的人
3.1 是,大功告成
3.2 否,将这个人的所有通讯好友加入队列, 并跳到第二步,递归
注意检查完之后,需要标记为已检查,不然 像下面这种情况可能会导致无线循环。
9 狄克斯特拉算法
段数最少,用广度优先搜索。路径最短, 用狄克斯特拉算法。
狄克斯特拉算法四个步骤:
1 找出最便宜的节点,既在最短时间内到达的节点。
2 更新该节点的邻居开销
3 重复这个过程,直到对图中的每个节点都这样做了。
4 计算最终路径。
狄克斯特拉算法每条边都有关联数字的图,这些数字称为权重。
算法实现:
广度优先搜索用于在非加权图中查找最短路径,狄克斯特拉用于加权图中查找最短路径。
10 贪婪算法
NP完全问题:没有快速算法的问题。
识别NP 完全问题,面临NP 完全问题时(教室调度,背包问题,集合覆盖问题),最佳的做法是使用近似算法,贪婪算法易于实现,运行速度快,是不错的 近似算法。
贪婪算法:一种非常简单的问题解决策略,每一步都采用最优解,每步都选择局部最优解的话,得到的就是全局最优解。
11 动态优化问题
动态规划:它将问题分成小问题,先着手解决这些小问题。
算法步骤:在每一行,可偷的商品都为当前行以及之前各行的商品,然后填充网格,最后得到如下网格。
K最近邻算法
K最近邻算法(KNN算法):判断这个水果是柚子还是橙子,看这三个邻居中, 柚子多还是橙子多,如果柚子多,那么就把这个水果归类为柚子, 这就是K最近邻算法。
创建系统推荐:假设Netflix 要给用户priyanka 推荐电影,可以找出喜好与他最接近的五位用户。假设与他喜好接近的五位用户都喜欢这部电影,那么就将其推荐给priyanka.
那么如何确定喜好接近呢? 特征抽取
机器学习:浏览大量的数字图像,将这些特征提取出来(喂养),遇到新图像时,提取该图像的特诊,再找它最近的邻居。
12 其他十种类型算法
二叉查找树:对于其每个节点,左子节点都比它小,而右子节点的值都比它大。二叉查找树大O时间查找和二分法一样。对于有序数组而言,大0 时间查找都一样,但是插入删除要比有序数组快的多。
反向索引:创建一个散列表,将单词映射到包含它的界面。当用户输入某个单词时,查看散列表,哪些页面包含这个单词,在搜索经常用到。
傅里叶变换: 将时域信号转换成频域信号。使用范围极广,比如歌曲调音,JPG 压缩等等。
并行算法:现在台式机或者笔记本采用多核处理器,为提高算法速度,需要让它们在多个内核中并行执行。
分布式算法:在并行算法只需二到四个内核时,完全可以在笔记本运行它,但如果数百个核呢,在这种情况下,可让算法在多台计算机上运行,这就是分布式算法。
布隆过滤器:它是一种概率型数据结构,它提供的答案有可能不对,但很有可能是正确的,相比散列表的绝对准确,布隆过滤器速度要快的多。
SHA:给定一串字符或者文件生成唯一的SHA 值,SHA是一系列算法,SHA-0,SHA-1,SHA-2和SHA-3.
Simhash:它是局部敏感的散列算法,不同于SHA ,哪怕一个字符不同,得到的值SHA完全不同,Simhash 一个字符不同,得到的字符串只有细微的差异。
Diffie-Hellman:使用两个密钥,公钥和私钥,故名思意,公钥的公开的,加密后的消息只有使用私钥才能打开。
线性规划:用于在给定的约束条件下,最大限度的改善指定的指标。另外所有图的算法都可以使用线性规划来实现,线性规划是一个宽泛得多的框架,图的问题只是其中一个的子集。