好多大的公司都問(wèn)算法,那么在這里總結(jié)一下。
其實(shí)我個(gè)人覺(jué)得在實(shí)際項(xiàng)目開(kāi)發(fā)中并沒(méi)有用到很多的算法, 可能是我們的項(xiàng)目原因吧,就連平時(shí)見(jiàn)的最多的冒泡排序也沒(méi)有用上,但是面試的時(shí)候會(huì)問(wèn)到,排序、查找、刪除、插入等等,還有就是各大算法的思想和算法的實(shí)現(xiàn),那好吧,不積跬步,無(wú)以至千里,總是感覺(jué)算法很深?yuàn)W,那咱們一起掀開(kāi)算法的神秘面紗吧。
說(shuō)到算法,不得不提到的就是時(shí)間復(fù)雜度和空間復(fù)雜度。這兩個(gè)度是什么呢,簡(jiǎn)單的說(shuō)就是這么多算法,總有個(gè)高低之分吧,看看誰(shuí)更牛逼!自古就有“文無(wú)第一,武無(wú)第二”一說(shuō),所以各種算法到了一起總要“攀比”一下誰(shuí)更牛逼,官方語(yǔ)言這樣描述的“算法的質(zhì)量?jī)?yōu)劣將影響到算法乃至程序的效率”。那么就有了衡量的標(biāo)準(zhǔn),就是這兩個(gè)度。
(1)時(shí)間復(fù)雜度:一般情況下,算法中基本操作重復(fù)執(zhí)行的次數(shù)是問(wèn)題規(guī)模n的某個(gè)函數(shù),用T(n)表示,若有某個(gè)輔助函數(shù)f(n),使得當(dāng)n趨近于無(wú)窮大時(shí),T(n)/f(n)的極限值為不等于零的常數(shù),則稱f(n)是T(n)的同數(shù)量級(jí)函數(shù)。記作T(n)=O(f(n)),稱O(f(n)) 為算法的漸進(jìn)時(shí)間復(fù)雜度,簡(jiǎn)稱時(shí)間復(fù)雜度。
分析:隨著模塊n的增大,算法執(zhí)行的時(shí)間的增長(zhǎng)率和 f(n) 的增長(zhǎng)率成正比,所以 f(n) 越小,算法的時(shí)間復(fù)雜度越低,算法的效率越高。
2. 在計(jì)算時(shí)間復(fù)雜度的時(shí)候,先找出算法的基本操作,然后根據(jù)相應(yīng)的各語(yǔ)句確定它的執(zhí)行次數(shù),再找出 T(n) 的同數(shù)量級(jí)(它的同數(shù)量級(jí)有以下:1,log2n,n,n log2n ,n的平方,n的三次方,2的n次方,n!),找出后,f(n) = 該數(shù)量級(jí),若 T(n)/f(n) 求極限可得到一常數(shù)c,則時(shí)間復(fù)雜度T(n) = O(f(n))
根據(jù)“同中求異,異中求同”,我們?cè)O(shè)定x軸上邊去一個(gè)x=10,可以得出如下結(jié)論:
(2)空間復(fù)雜度:類似于時(shí)間復(fù)雜度的討論,一個(gè)算法的空間復(fù)雜度(SpaceComplexity)S(n)定義為該算法所耗費(fèi)的存儲(chǔ)空間,它也是問(wèn)題規(guī)模n的函數(shù)。漸近空間復(fù)雜度也常常簡(jiǎn)稱為空間復(fù)雜度。空間復(fù)雜度(SpaceComplexity)是對(duì)一個(gè)算法在運(yùn)行過(guò)程中臨時(shí)占用存儲(chǔ)空間大小的量度。一個(gè)算法在計(jì)算機(jī)存儲(chǔ)器上所占用的存儲(chǔ)空間,包括存儲(chǔ)算法本身所占用的存儲(chǔ)空間,算法的輸入輸出數(shù)據(jù)所占用的存儲(chǔ)空間和算法在運(yùn)行過(guò)程中臨時(shí)占用的存儲(chǔ)空間這三個(gè)方面。
關(guān)于兩個(gè)復(fù)雜度,借用一個(gè)網(wǎng)友的說(shuō)法就是“愚公的精神固然可敬,但是推土機(jī)和炸藥可能是更加實(shí)在和聰明”。
好了,我們大概明白了什么是時(shí)間復(fù)雜度和空間復(fù)雜度了,(說(shuō)實(shí)在的,光看概念我真的不怎么明白)那么我們經(jīng)常用的算法里面到底誰(shuí)更厲害呢?!下圖是咱們經(jīng)常用的排序的一個(gè)比較(誰(shuí)能告訴我timsort是啥?桶排序是啥?基數(shù)排序是啥?為啥沒(méi)有二分法?還有二叉樹(shù)呢?)
上圖的紅黃綠棕色代表的意思就是,綠色效率更高,黃色一般,紅色最差。
先來(lái)一發(fā)二分法的。
二分法查找
二分法查找的思想:首先保證這個(gè)數(shù)組是一個(gè)有序的數(shù)組。首先取到第一個(gè)數(shù)和最后一個(gè)數(shù)的下標(biāo):min、max,然后取到中間數(shù)的下標(biāo)mid=(min+max)/2,那么我們就能拿到中間數(shù)的數(shù)值,和我們要查找的數(shù)searchNum進(jìn)行比較,兩種結(jié)果:
(1)中間的數(shù)值大,那么searchNum就是中間數(shù)之前,那么我們將之前的max=mid-1,(到這里就做到了二分:將數(shù)組的后半部分直接舍棄,不做比較,這也是數(shù)組必須是有序數(shù)組的原因。)再進(jìn)行上述操作進(jìn)行比較,直到作出判斷。
(2)中間的數(shù)值小,那么searchNum就是中間數(shù)之后,那么我們將之前的min=mid+1,(到這里就做到了二分:將數(shù)組的前半部分直接舍棄,不做比較,這也是數(shù)組必須是有序數(shù)組的原因。)再進(jìn)行上述操作進(jìn)行比較,直到作出判斷。
二分法排序
二分法排序思想:主要思想在于while:start和middle之間的“勾當(dāng)”將數(shù)組的前半部分排好序的直接忽略掉,不進(jìn)行比較。
我們給一個(gè)數(shù)組@[@12,@3,@23,@34,@35,@99,@98,@43];
(1)當(dāng)index=0的時(shí)候,start=0,end=-1,middle=0,temp=12,根據(jù)比較會(huì)進(jìn)入while,此時(shí)middle=0,if比較的時(shí)候不成立,跳出while。j=-1=end,那么for循環(huán)不進(jìn)入。repleace里面index=0和temp交換,其實(shí)就是12自己和自己交換。
(2)當(dāng)index=1的時(shí)候,start=0,end=0,middle=0,temp=3,根據(jù)比較會(huì)進(jìn)入while,此時(shí)middle=0,if比較成立,進(jìn)入end=-1。j=0>end,則result[1]=result[0],此時(shí)數(shù)組第二數(shù)變成了12,再for循環(huán),j=-1,不成立,跳出for循環(huán)。repleace里面index=0和temp交換,那么數(shù)組第一個(gè)數(shù)變成了3,完成了交換。
......
(3)當(dāng)index=7的時(shí)候,數(shù)組變成了@[@3,@12,@23,@34,@35,@98,@99,@43];start=0,end=6,middle=0,temp=43,根據(jù)比較會(huì)進(jìn)入while,此時(shí)middle=3,if比較得:34<43,進(jìn)入else,start=4,再次進(jìn)入while,middle=5,(二分法在此體現(xiàn):start和middle之間將數(shù)組的前半部分排好序的直接忽略掉,不進(jìn)行比較)。if 比較得:98>43,則end=4,再次比較start>end,所以跳出while進(jìn)入for循環(huán)。j=6,result[7]=result[6],此時(shí)數(shù)組最后一個(gè)數(shù)是99。然后for循環(huán)進(jìn)行:j--得到j(luò)=5,j>end,result[6]=result[5],此時(shí)數(shù)組倒數(shù)第二個(gè)數(shù)數(shù)是98,然后for循環(huán)進(jìn)行:j--得到j(luò)=4,j=end,此時(shí)跳出ror循環(huán)。repleace里面index=5和temp交換,即43變成了在index為5的位置,也就是倒數(shù)第三個(gè)數(shù)。
此時(shí)數(shù)組排序完成。
注意:對(duì)于NSMutableArray的replaceObjectAtIndex:方法,用B替換了A,擔(dān)心A的去向?不用擔(dān)心,調(diào)用該方法的時(shí)候會(huì)對(duì)A自動(dòng)調(diào)用release,所以我們不必?fù)?dān)心A的去向。
再來(lái)一發(fā)冒泡:
冒泡排序
冒泡排序的思想就是(排序完成后是由大到小的順序):1.比較相鄰的元素。如果前面的比后邊的小,就交換他們兩個(gè)。對(duì)每一對(duì)相鄰元素作同樣的工作,從開(kāi)始第一對(duì)到結(jié)尾的最后一對(duì)。在這一點(diǎn),最后的元素應(yīng)該會(huì)是最小的數(shù)。針對(duì)所有的元素重復(fù)以上的步驟,除了最后一個(gè)。持續(xù)每次對(duì)越來(lái)越少的元素重復(fù)上面的步驟,直到?jīng)]有任何一對(duì)數(shù)字需要比較。
簡(jiǎn)單的說(shuō)就是:比較相鄰元素,進(jìn)行交換。
簡(jiǎn)單排序
簡(jiǎn)單排序的核心思想:找準(zhǔn)時(shí)機(jī)、再做替換
和冒泡相比,冒泡每次都做替換,簡(jiǎn)單排序是遍歷數(shù)組后找到最小元素的下標(biāo)后再做替換。
簡(jiǎn)單排序的時(shí)間復(fù)雜度也是O(N^2)。雖然簡(jiǎn)單排序和冒泡排序的時(shí)間復(fù)雜度一樣,但是簡(jiǎn)單排序在性能方面還是好很多的,交換次數(shù)沒(méi)像冒泡那么頻繁。
希爾排序
希爾排序是一個(gè)基于插入排序的改進(jìn)型插入排序算法。由于插入排序一次只能交換相鄰的元素,因此元素只能一點(diǎn)點(diǎn)的從數(shù)組的一端移動(dòng)到另一端。如果最小的元素在數(shù)組的末尾的話,那就需要N-1次移動(dòng),因此對(duì)于插入排序的效率是非常低的。
注意:實(shí)際就是a[0]和a[h]比較,a[1]和a[h+1]比較。。。每比較完一輪后,就縮小h的值。
理解:步長(zhǎng)是決定希爾排序時(shí)間復(fù)雜度的關(guān)鍵,但是究竟應(yīng)該選擇什么樣的步長(zhǎng)才是最好的,目前還是一個(gè)數(shù)學(xué)難題。不過(guò)大量的研究數(shù)據(jù)表明,步長(zhǎng)與時(shí)間復(fù)雜度存在以下關(guān)系:(看不懂下邊說(shuō)的是啥意思啊)
Shell排序通過(guò)將數(shù)據(jù)分成不同的組,先對(duì)每一組進(jìn)行排序,然后再對(duì)所有的元素進(jìn)行一次插入排序,以減少數(shù)據(jù)交換和移動(dòng)的次數(shù)。平均效率是O(nlogn)。其中分組的合理性會(huì)對(duì)算法產(chǎn)生重要的影響。
按照不同步長(zhǎng)對(duì)元素進(jìn)行插入排序,當(dāng)剛開(kāi)始元素很無(wú)序的時(shí)候,步長(zhǎng)最大,所以插入排序的元素個(gè)數(shù)很少,速度很快;當(dāng)元素基本有序了,步長(zhǎng)很小,插入排序?qū)τ谟行虻男蛄行屎芨摺K裕柵判虻臅r(shí)間復(fù)雜度會(huì)比o(n^2)好一些。由于多次插入排序,我們知道一次插入排序是穩(wěn)定的,不會(huì)改變相同元素的相對(duì)順序,但在不同的插入排序過(guò)程中,相同的元素可能在各自的插入排序中移動(dòng),最后其穩(wěn)定性就會(huì)被打亂,所以shell排序是不穩(wěn)定的。
Shell排序比冒泡排序快5倍,比插入排序大致快2倍。Shell排序比起QuickSort,MergeSort,HeapSort慢很多。但是它相對(duì)比較簡(jiǎn)單,它適合于數(shù)據(jù)量在5000以下并且速度并不是特別重要的場(chǎng)合。它對(duì)于數(shù)據(jù)量較小的數(shù)列重復(fù)排序是非常好的。
最后,哪里不對(duì)的地方可以給我留言,我會(huì)及時(shí)改進(jìn)的,謝謝大家。