好多大的公司都问算法,那么在这里总结一下。
其实我个人觉得在实际项目开发中并没有用到很多的算法, 可能是我们的项目原因吧,就连平时见的最多的冒泡排序也没有用上,但是面试的时候会问到,排序、查找、删除、插入等等,还有就是各大算法的思想和算法的实现,那好吧,不积跬步,无以至千里,总是感觉算法很深奥,那咱们一起掀开算法的神秘面纱吧。
说到算法,不得不提到的就是时间复杂度和空间复杂度。这两个度是什么呢,简单的说就是这么多算法,总有个高低之分吧,看看谁更牛逼!自古就有“文无第一,武无第二”一说,所以各种算法到了一起总要“攀比”一下谁更牛逼,官方语言这样描述的“算法的质量优劣将影响到算法乃至程序的效率”。那么就有了衡量的标准,就是这两个度。
(1)时间复杂度:一般情况下,算法中基本操作重复执行的次数是问题规模n的某个函数,用T(n)表示,若有某个辅助函数f(n),使得当n趋近于无穷大时,T(n)/f(n)的极限值为不等于零的常数,则称f(n)是T(n)的同数量级函数。记作T(n)=O(f(n)),称O(f(n)) 为算法的渐进时间复杂度,简称时间复杂度。
分析:随着模块n的增大,算法执行的时间的增长率和 f(n) 的增长率成正比,所以 f(n) 越小,算法的时间复杂度越低,算法的效率越高。
2. 在计算时间复杂度的时候,先找出算法的基本操作,然后根据相应的各语句确定它的执行次数,再找出 T(n) 的同数量级(它的同数量级有以下:1,log2n,n,n log2n ,n的平方,n的三次方,2的n次方,n!),找出后,f(n) = 该数量级,若 T(n)/f(n) 求极限可得到一常数c,则时间复杂度T(n) = O(f(n))
根据“同中求异,异中求同”,我们设定x轴上边去一个x=10,可以得出如下结论:
(2)空间复杂度:类似于时间复杂度的讨论,一个算法的空间复杂度(SpaceComplexity)S(n)定义为该算法所耗费的存储空间,它也是问题规模n的函数。渐近空间复杂度也常常简称为空间复杂度。空间复杂度(SpaceComplexity)是对一个算法在运行过程中临时占用存储空间大小的量度。一个算法在计算机存储器上所占用的存储空间,包括存储算法本身所占用的存储空间,算法的输入输出数据所占用的存储空间和算法在运行过程中临时占用的存储空间这三个方面。
关于两个复杂度,借用一个网友的说法就是“愚公的精神固然可敬,但是推土机和炸药可能是更加实在和聪明”。
好了,我们大概明白了什么是时间复杂度和空间复杂度了,(说实在的,光看概念我真的不怎么明白)那么我们经常用的算法里面到底谁更厉害呢?!下图是咱们经常用的排序的一个比较(谁能告诉我timsort是啥?桶排序是啥?基数排序是啥?为啥没有二分法?还有二叉树呢?)
上图的红黄绿棕色代表的意思就是,绿色效率更高,黄色一般,红色最差。
先来一发二分法的。
二分法查找
二分法查找的思想:首先保证这个数组是一个有序的数组。首先取到第一个数和最后一个数的下标:min、max,然后取到中间数的下标mid=(min+max)/2,那么我们就能拿到中间数的数值,和我们要查找的数searchNum进行比较,两种结果:
(1)中间的数值大,那么searchNum就是中间数之前,那么我们将之前的max=mid-1,(到这里就做到了二分:将数组的后半部分直接舍弃,不做比较,这也是数组必须是有序数组的原因。)再进行上述操作进行比较,直到作出判断。
(2)中间的数值小,那么searchNum就是中间数之后,那么我们将之前的min=mid+1,(到这里就做到了二分:将数组的前半部分直接舍弃,不做比较,这也是数组必须是有序数组的原因。)再进行上述操作进行比较,直到作出判断。
二分法排序
二分法排序思想:主要思想在于while:start和middle之间的“勾当”将数组的前半部分排好序的直接忽略掉,不进行比较。
我们给一个数组@[@12,@3,@23,@34,@35,@99,@98,@43];
(1)当index=0的时候,start=0,end=-1,middle=0,temp=12,根据比较会进入while,此时middle=0,if比较的时候不成立,跳出while。j=-1=end,那么for循环不进入。repleace里面index=0和temp交换,其实就是12自己和自己交换。
(2)当index=1的时候,start=0,end=0,middle=0,temp=3,根据比较会进入while,此时middle=0,if比较成立,进入end=-1。j=0>end,则result[1]=result[0],此时数组第二数变成了12,再for循环,j=-1,不成立,跳出for循环。repleace里面index=0和temp交换,那么数组第一个数变成了3,完成了交换。
......
(3)当index=7的时候,数组变成了@[@3,@12,@23,@34,@35,@98,@99,@43];start=0,end=6,middle=0,temp=43,根据比较会进入while,此时middle=3,if比较得:34<43,进入else,start=4,再次进入while,middle=5,(二分法在此体现:start和middle之间将数组的前半部分排好序的直接忽略掉,不进行比较)。if 比较得:98>43,则end=4,再次比较start>end,所以跳出while进入for循环。j=6,result[7]=result[6],此时数组最后一个数是99。然后for循环进行:j--得到j=5,j>end,result[6]=result[5],此时数组倒数第二个数数是98,然后for循环进行:j--得到j=4,j=end,此时跳出ror循环。repleace里面index=5和temp交换,即43变成了在index为5的位置,也就是倒数第三个数。
此时数组排序完成。
注意:对于NSMutableArray的replaceObjectAtIndex:方法,用B替换了A,担心A的去向?不用担心,调用该方法的时候会对A自动调用release,所以我们不必担心A的去向。
再来一发冒泡:
冒泡排序
冒泡排序的思想就是(排序完成后是由大到小的顺序):1.比较相邻的元素。如果前面的比后边的小,就交换他们两个。对每一对相邻元素作同样的工作,从开始第一对到结尾的最后一对。在这一点,最后的元素应该会是最小的数。针对所有的元素重复以上的步骤,除了最后一个。持续每次对越来越少的元素重复上面的步骤,直到没有任何一对数字需要比较。
简单的说就是:比较相邻元素,进行交换。
简单排序
简单排序的核心思想:找准时机、再做替换
和冒泡相比,冒泡每次都做替换,简单排序是遍历数组后找到最小元素的下标后再做替换。
简单排序的时间复杂度也是O(N^2)。虽然简单排序和冒泡排序的时间复杂度一样,但是简单排序在性能方面还是好很多的,交换次数没像冒泡那么频繁。
希尔排序
希尔排序是一个基于插入排序的改进型插入排序算法。由于插入排序一次只能交换相邻的元素,因此元素只能一点点的从数组的一端移动到另一端。如果最小的元素在数组的末尾的话,那就需要N-1次移动,因此对于插入排序的效率是非常低的。
注意:实际就是a[0]和a[h]比较,a[1]和a[h+1]比较。。。每比较完一轮后,就缩小h的值。
理解:步长是决定希尔排序时间复杂度的关键,但是究竟应该选择什么样的步长才是最好的,目前还是一个数学难题。不过大量的研究数据表明,步长与时间复杂度存在以下关系:(看不懂下边说的是啥意思啊)
Shell排序通过将数据分成不同的组,先对每一组进行排序,然后再对所有的元素进行一次插入排序,以减少数据交换和移动的次数。平均效率是O(nlogn)。其中分组的合理性会对算法产生重要的影响。
按照不同步长对元素进行插入排序,当刚开始元素很无序的时候,步长最大,所以插入排序的元素个数很少,速度很快;当元素基本有序了,步长很小,插入排序对于有序的序列效率很高。所以,希尔排序的时间复杂度会比o(n^2)好一些。由于多次插入排序,我们知道一次插入排序是稳定的,不会改变相同元素的相对顺序,但在不同的插入排序过程中,相同的元素可能在各自的插入排序中移动,最后其稳定性就会被打乱,所以shell排序是不稳定的。
Shell排序比冒泡排序快5倍,比插入排序大致快2倍。Shell排序比起QuickSort,MergeSort,HeapSort慢很多。但是它相对比较简单,它适合于数据量在5000以下并且速度并不是特别重要的场合。它对于数据量较小的数列重复排序是非常好的。
最后,哪里不对的地方可以给我留言,我会及时改进的,谢谢大家。