从很多途径当中,看到过这本书的知识点,是一本很有趣的算法入门书籍,最近花费了几天的时间将其阅读完,总想着总结一下这本书的算法知识点,分享给大家,也让自己掌握地更加踏实一点.
算法:一组完成任何任务的指令
算法这玩意,在保证满足条件,并且不浪费内存的情况下,要尽可能速度更快,这就是我们要使用和选择算法的原因.
在这本书当中称为运行时间,在下面每一种算法的过程当中都尽可能会涉及到.
二分查找
二分查找的对象是一组有序的数组,记住必须是有序的
在这组数组当中定义定义索引0是low,而索引(数组最大索引)为high
当low<=high的时候,mid=(low+high)/2
这里一看上面书籍就写错了
如果说中间索引对应的数值刚好等于我们要查找的数字
则直接返回中间索引数值就好了
否则判断中间索引对应的数值和我们要查找数字的大小关系
如果大于,则high=mid-1,相当于我们搜索low到mid-1这个范围
如果小于,则low=mid+1,相当于我们搜索mid+1到high这个范围
继续返回上面的while,迭代就行
如果运行结束,数组当中没有任何数值等于我们要查找的数值
则返回None
运行时间:
线性时间描述为O(n)
二分查找的运行时间描述为O(logn)
//O(n*logn)这样的算法包括快速排序—一种较快的排序算法
//O(sqrt(n)),这样的算法包括将介绍的选择排序----一种速度较慢的排序算法
O(n!)这样的算法包括将介绍的旅行商的解方案—一种速度非常慢的算法<不建议去看和使用>—正常情况下用不到的
ex:大O表示法是一种特殊的表示法,指出了算法的速度有多快.
图例如下:
把纵坐标想成时间,去看这几个图就行了
选择排序
需要将数据存储到内存时,你请求计算机提供存储空间,计算机给你一个存储地址。需要存储多项数据时,有两种基本方式——数组和链表。
ex:数组的读取速度很快,但是链表的插入和删除速度很快.
ex:在同一个数组当中,所有元素的类型都必须是相同的
使用数组意味着事项在内存当中都是相互连接的,或者说紧靠在一起的
同时数组的好处在于可以挑选出其中的任何一个事项目,例如a[1]或者a[10],可以直接输出.
但数组的插入和删除是相当麻烦的,例如一个a[5]的数组,存储有5个数据,如果删除掉开头或者结尾的数据,还比较简单,但是如果删除掉中间的数据,例如a[3],就需要将原来的a[4]和a[5]同时提前,插入同样复杂.
而链表中的元素可以存储在内存的任何敌方,不需要相互连接或者紧靠,但链表的每个元素都存储了下一个元素的地址
链表无法像数组那样可以直接挑选出,其中任何一个事项的数据,必须从头开始串联,一直到所寻找的数据为止.
链表的插入和删除相对简单,例如5个数据,我需要删除第3个数据,则只需要使得第二个数据的下一个地址为第四个数据.原来的第三个数据自动删除
选择排序:
就是数组从头到尾,依次比较大小,每次都找出数组当中最大或者最小的数据放在数组的一端进行存储,直到全部循环完毕.
如果是二维数组,是不是让我们想起来了冒泡法:
//采用冒泡排序法
for(j=1;j<i;j++)
{
for(k=0;k<i-j;k++)//从第一个数开始,一直到倒数第二个数,比较大小
//然后是第一个数开始,一直到倒数第三个数,比较大小,依次
{
if(a[k+1]<a[k])
{
temp=a[k];
a[k]=a[k+1];
a[k+1]=temp;
}
}
}
递归
递归总而言之就是一种分而治之的策略
盒子里面找盒子,直到找到盒子里面的钥匙为止.
或者说递归指的就是调用自己的函数
第一种方案是递归,而第二种方案则是循环.
ex:使用循环的性能更好,但是如果使用递归,程序更加容易理解.
递归函数当中有两个条件,一个是基线条件,一个是递归条件,递归条件很容易理解,就是持续循环,那什么时候判断循环结束呢?这个就叫做基线条件
ex:调用栈
举例说明栈这个基本概念:一叠便条要简单很多,插入的待办事项放在清单的最前面;读取待办事项时,你只读取最上面的那个,并将其删除.也就是先进后出.
因此这个代办事项清单只有两种操作:压入(插入)和弹出(删除并读取)
ex:调用栈,每个函数都需要占用一定的内存,这将导致运行速度变慢.可以转换为循环重新编写代码.或者使用尾递归<这里不做说明>
快速排序
一种优雅的排序算法
//第一种方法,采用快速排序的方法
int cmp_int(const void* _a,const void* _b)
{
int* a=(int*)_a; //强制类型转换
int* b=(int*)_b;
return *a - *b; //从小到大进行排序,升序,如果是降序,则修改为*b - *a;
}
qsort(a,n,sizeof(a[0]),cmp_int);
是一种分开来治理的算法,可以直接使用
散列表
简单说,就是存储数组索引的一组代号
散列表非常适合于防止重复
思考一下,投票的时候,我们无法控制每个人只投了一票,所以我们可以采用散列表将每个人的姓名进行存储,如果出现重复的则进行条件处理.
例如:
上图如果无法理解,想一想手机的通讯录,里面有联系人和电话号码.我们不可能记住每一个人的电话号码,但是我们知道要给谁打电话,而通讯录里面的用户名就是散列表
要注意使用散列表的时候,尽可能避免冲突和保持性能:
1.较低的填装因子<=0.7
2.良好的散列函数
广度优先搜索
BFS算法:适合回答"到X的最短路径是什么,或者说到X是否存在路径"这样的问题
从起点出发,一步可以到达的地方进行存储
二步可以到达的地方进行存储
三步
四步
一直到"n步"数组当中存在有终点,则找到了从起点到终点最短路径为n
ex:队列,不同于栈,队列是一种先进先出的结构<入队和出队>
注意此处是先把第一关系的循环完,然后再执行第二关系循环.
ex:一个人可能即是a的朋友又是b的朋友,那么我们将产生重复,为避免这种情况,我们需要调用一个检查函数,如果已经执行过,则进行标记.
运行时间:,广度优先搜索的运行时间为O(人数 + 边数),这通常写作O(V + E),其中V为顶点(vertice)数,E为边数。
无法往后指的图叫做树,例如族谱
狄克斯特拉<后续我将提供一道QT,供大家理解>
找到加权图中前往X的最短路径
如果有负权边,就不能使用狄克斯特拉算法。
在包含负权边的图中,要找出最短路径,可使用另一种算法——贝尔曼–福德算法
贪婪算法
NP完全问题,每一步直接找到最小的或者最大的,然后加起来就是全部最小的或者最大的
局部全部都小构成了全局尽可能小
动态规划
把原来数组和所要求的结果,构成一个新的数组,称为dp数组,然后找到最后一行或者第一行当中数据的最优解,则是我们要寻找的数据.
K最近邻算法
找案例A距离或者关系最近的另一个案例B,则B的特征可以代表案例A的特征.