整理:iOS開發算法資料

關於算法的想法

因爲面試可能須要手寫算法,網上搜羅了一些資料,整理了下算法的OC的實現代碼,雖然平時開發中通常用不到,可是多積累一些技術知識,仍是對之後發展大有裨益的git

github上搜集的幾大算法原理和實現代碼,只有JavaScript、Python、Go、Java的實現代碼github

算法文字理解和OC代碼實現

1. 冒泡排序算法(Bubble Sort)

相鄰元素進行比較,按照升序或者降序,交換兩個相鄰元素的位置 是一種「穩定排序算法」web

1.1 網上文字理論

是一種簡單直觀的排序算法。它重複地走訪過要排序的數列,一次比較兩個元素,若是他們的順序錯誤就把他們交換過來。走訪數列的工做是重複地進行直到沒有再須要交換,也就是說該數列已經排序完成。這個算法的名字由來是由於越小的元素會經由交換慢慢「浮」到數列的頂端。
做爲最簡單的排序算法之一,冒泡排序給個人感受就像 Abandon 在單詞書裏出現的感受同樣,每次都在第一頁第一位,因此最熟悉。冒泡排序還有一種優化算法,就是立一個 flag,當在一趟序列遍歷中元素沒有發生交換,則證實該序列已經有序。但這種改進對於提高性能來講並無什麼太大做用。面試

1.2 算法步驟

  1. 比較相鄰的元素。若是第一個比第二個大,就交換他們兩個。
  2. 對每一對相鄰元素做一樣的工做,從開始第一對到結尾的最後一對。這步作完後,最後的元素會是最大的數。
  3. 針對全部的元素重複以上的步驟,除了最後一個。
  4. 持續每次對愈來愈少的元素重複上面的步驟,直到沒有任何一對數字須要比較。

1.3 動圖演示

1.4 何時最快

當輸入的數據已是正序時。算法

1.5 何時最慢

當輸入的數據是反序時。shell

1.6 冒泡排序代碼示例

- (void)bubbleSortWithArray:(NSMutableArray *)array {
    for (int i = 0; i < array.count - 1; i++) {
         //外層for循環控制循環次數
        for (int j = 0; j < array.count - 1 - i; j++) {
            //內層for循環控制交換次數
            if ([array[j] integerValue] > [array[j + 1] integerValue]) {
                [array exchangeObjectAtIndex:j withObjectAtIndex:j + 1];
            }
        }
    }
}

2. 快速排序算法(quick sort)

快速排序圖文理解,經過哨兵站崗理解快速排序數組

2.1 網上文字理解

快速排序是由東尼·霍爾所發展的一種排序算法。在平均情況下,排序 n 個項目要 Ο(nlogn) 次比較。在最壞情況下則須要 Ο(n2) 次比較,但這種情況並不常見。事實上,快速排序一般明顯比其餘 Ο(nlogn) 算法更快,由於它的內部循環(inner loop)能夠在大部分的架構上頗有效率地被實現出來。數據結構

快速排序使用分治法(Divide and conquer)策略來把一個串行(list)分爲兩個子串行(sub-lists)。架構

快速排序又是一種分而治之思想在排序算法上的典型應用。本質上來看,快速排序應該算是在冒泡排序基礎上的遞歸分治法。ide

快速排序的名字起的是簡單粗暴,由於一聽到這個名字你就知道它存在的意義,就是快,並且效率高!它是處理大數據最快的排序算法之一了。雖然 Worst Case 的時間複雜度達到了 O(n²),可是人家就是優秀,在大多數狀況下都比平均時間複雜度爲 O(n logn) 的排序算法表現要更好,但是這是爲何呢,我也不知道。好在個人強迫症又犯了,查了 N 多資料終於在《算法藝術與信息學競賽》上找到了滿意的答案: 快速排序的最壞運行狀況是 O(n²),好比說順序數列的快排。但它的平攤指望時間是 O(nlogn),且 O(nlogn) 記號中隱含的常數因子很小,比複雜度穩定等於 O(nlogn) 的歸併排序要小不少。因此,對絕大多數順序性較弱的隨機數列而言,快速排序老是優於歸併排序。

2.2 算法步驟

  1. 從數列中挑出一個元素,稱爲 「基準」(pivot);
  2. 從新排序數列,全部元素比基準值小的擺放在基準前面,全部元素比基準值大的擺在基準的後面(相同的數能夠到任一邊)。在這個分區退出以後,該基準就處於數列的中間位置。這個稱爲分區(partition)操做;
  3. 遞歸地(recursive)把小於基準值元素的子數列和大於基準值元素的子數列排序;

遞歸的最底部情形,是數列的大小是零或一,也就是永遠都已經被排序好了。雖然一直遞歸下去,可是這個算法總會退出,由於在每次的迭代(iteration)中,它至少會把一個元素擺到它最後的位置去。

2.3 動圖演示

image

2.4 快速排序代碼示例

- (void)quickSortArray:(NSMutableArray *)array
            leftIndex:(NSInteger)left
           rightIndex:(NSInteger)right {
   if (left > right) {
       return;
   }
   NSInteger i = left;
   NSInteger j = right;
   //記錄基準數 pivoty
   NSInteger key = [array[i] integerValue];
   while (i < j) {
       //首先從右邊j開始查找(從最右邊往左找)比基準數(key)小的值<---
       while (i < j && key <= [array[j] integerValue]) {
           j--;
       }
       //若是從右邊j開始查找的值[array[j] integerValue]比基準數小,則將查找的小值調換到i的位置
       if (i < j) {
           array[i] = array[j];
       }

       //從i的右邊往右查找到一個比基準數小的值時,就從i開始日後找比基準數大的值 --->
       while (i < j && [array[i] integerValue] <= key) {
           i++;
       }
       //若是從i的右邊往右查找的值[array[i] integerValue]比基準數大,則將查找的大值調換到j的位置
       if (i < j) {
           array[j] = array[i];
       }
   }
   //將基準數放到正確的位置,----改變的是基準值的位置(數組下標)---
   array[i] = @(key);
   //遞歸排序
   //將i左邊的數從新排序
   [self quickSortArray:array leftIndex:left rightIndex:i - 1];
   //將i右邊的數從新排序
   [self quickSortArray:array leftIndex:i + 1 rightIndex:right];
}

3. 選擇排序算法(select sort)

它的改進(相比較冒泡算法)在於:先並不急於調換位置,先從A[0]開始逐個檢查,看哪一個數最小就記下該數所在的位置P,等一躺掃描完畢,再把A[P]和A[0]對調,這時A[0]到A[n]中最小的數據就換到了最前面的位置。是一個「不穩定排序算法」

它是一種簡單直觀的排序算法,不管什麼數據進去都是 O(n²) 的時間複雜度。因此用到它的時候,數據規模越小越好。惟一的好處可能就是不佔用額外的內存空間。

選擇排序算法一: 直接選擇排序(straight select sort)

3.1 算法步驟

  1. 首先在未排序序列中找到最小(大)元素,存放到排序序列的起始位置
  2. 再從剩餘未排序元素中繼續尋找最小(大)元素,而後放到已排序序列的末尾。
  3. 重複第二步,直到全部元素均排序完畢。

3.2 動圖演示

image

3.3 直接選擇排序示例代碼

- (void)selectSortWithArray:(NSMutableArray *)array {
    for (int i = 0; i < array.count; i++) {
        for (int j = i + 1; j < array.count; j++) {
            if (array[i] > array[j]) {
                [array exchangeObjectAtIndex:i withObjectAtIndex:j];
            }
        }
    }
}

選擇排序算法二:堆排序(heap sort 涉及到徹底二叉樹的概念)

堆排序是指利用堆這種數據結構所設計的一種排序算法。堆積是一個近似徹底二叉樹的結構,並同時知足堆積的性質:即子結點的鍵值或索引老是小於(或者大於)它的父節點。堆排序能夠說是一種利用堆的概念來排序的選擇排序。分爲兩種方法:

  1. 大頂堆:每一個節點的值都大於或等於其子節點的值,在堆排序算法中用於升序排列;
  2. 小頂堆:每一個節點的值都小於或等於其子節點的值,在堆排序算法中用於降序排列;
    堆排序的平均時間複雜度爲 Ο(nlogn)。

算法步驟

  1. 建立一個堆 H[0……n-1];
  2. 把堆首(最大值)和堆尾互換;
  3. 把堆的尺寸縮小 1,並調用 shift_down(0),目的是把新的數組頂端數據調整到相應位置;
  4. 重複步驟 2,直到堆的尺寸爲 1。

動圖演示

堆排序代碼示例

- (void)heapSortWithArray:(NSMutableArray *)array {
    //循環創建初始堆
    for (NSInteger i = array.count * 0.5; i >= 0; i--) {
        [self heapAdjustWithArray:array parentIndex:i length:array.count];
    }
    //進行n-1次循環,完成排序
    for (NSInteger j = array.count - 1; j > 0; j--) {
        //最後一個元素和第一個元素進行交換
        [array exchangeObjectAtIndex:j withObjectAtIndex:0];
        //篩選R[0]結點,獲得i-1個結點的堆
        [self heapAdjustWithArray:array parentIndex:0 length:j];
        NSLog(@"第%ld趟:", array.count - j);
        [self printHeapSortResult:array begin:0 end:array.count - 1];
    }
}

- (void)heapAdjustWithArray:(NSMutableArray *)array
                parentIndex:(NSInteger)parentIndex
                     length:(NSInteger)length {
    NSInteger temp = [array[parentIndex] integerValue]; //temp保存當前父結點
    NSInteger child = 2 * parentIndex + 1; //先得到左孩子

    while (child < length) {
        //若是有右孩子結點,而且右孩子結點的值大於左孩子結點,則選取右孩子結點
        if (child + 1 < length && [array[child] integerValue] < [array[child + 1] integerValue]) {
            child++;
        }

        //若是父結點的值已經大於孩子結點的值,則直接結束
        if (temp >= [array[child] integerValue]) {
            break;
        }

        //把孩子結點的值賦值給父結點
        array[parentIndex] = array[child];

        //選取孩子結點的左孩子結點,繼續向下篩選
        parentIndex = child;
        child = 2 * child + 1;
    }
    array[parentIndex] = @(temp);
}

- (void)printHeapSortResult:(NSMutableArray *)array
                      begin:(NSInteger)begin
                        end:(NSInteger)end {
    for (NSInteger i = 0; i < begin; i++) {

    }
    for (NSInteger i = begin; i <= end; i++) {

    }
    //打印堆排序
    NSLog(@"堆排序升序結果是--->%@",array);
}

4. 插入排序(insert sort)

4.1 網上文字理解

插入排序的代碼實現雖然沒有冒泡排序和選擇排序那麼簡單粗暴,但它的原理應該是最容易理解的了,由於只要打過撲克牌的人都應該可以秒懂。插入排序是一種最簡單直觀的排序算法,它的工做原理是經過構建有序序列,對於未排序數據,在已排序序列中從後向前掃描,找到相應位置並插入。

插入排序和冒泡排序同樣,也有一種優化算法,叫作拆半插入。

4.2 算法步驟

  1. 將第一待排序序列第一個元素看作一個有序序列,把第二個元素到最後一個元素當成是未排序序列。
  2. 從頭至尾依次掃描未排序序列,將掃描到的每一個元素插入有序序列的適當位置。(若是待插入的元素與有序序列中的某個元素相等,則將待插入元素插入到相等元素的後面)

4.3 動圖演示

4.4 插入排序代碼示例

- (void)insertSortWithArray:(NSMutableArray *)array {
    NSInteger j;
    for (NSInteger i = 1; i < array.count; i++) {
        //取出每個待插入的數據,從array[1]開始查找
        NSInteger temp = [array[i] integerValue];

        for (j = i - 1; j >= 0 && temp < [array[j] integerValue]; j--) {
            //若是以前的數比temp大,就將這個數日後移動一個位置,留出空來讓temp插入,和整理撲克牌相似
            [array[j + 1]  integerValue] = [array[j] integerValue]];
            array[j] = [NSNumber numberWithInteger:temp];
        }
    }
}

歸併排序、希爾排序、基數排序、桶排序、計數排序幾個不經常使用的算法,算是高級算法吧,具體是否是菜鳥本人以爲仍是隻看得懂文字,具體文字理論和實現原理還有待進一步學習,網上搜羅了不少資料說,通常須要掌握幾種經常使用的算法冒泡、快速、插入、選擇就夠了,我以爲技術仍是多多益善,固然前提是徹底掌握了,可以手寫,而且可以說出道理是最好的,否則都是臨時記憶,好吧,我目前理解的也是不夠深透,仍是網上記錄一些筆記,本身也好臨摹學習下,方便往後能消化這些計算機經常使用的算法

做爲一個開發者,有一個學習的氛圍跟一個交流圈子特別重要,這是一個個人iOS交流羣:519832104 無論你是小白仍是大牛歡迎入駐,分享經驗,討論技術,你們一塊兒交流學習成長!

另附上一份各好友收集的大廠面試題,須要iOS開發學習資料、面試真題,能夠添加iOS開發進階交流羣,進羣可自行下載!

點擊此處,當即與iOS大牛交流學習

5. 歸併排序(merge sort)

5.1 網上文字理解

歸併排序(Merge sort)是創建在歸併操做上的一種有效的排序算法。該算法是採用分治法(Divide and Conquer)的一個很是典型的應用。

做爲一種典型的分而治之思想的算法應用,歸併排序的實現由兩種方法:

  • 自上而下的遞歸(全部遞歸的方法均可以用迭代重寫,因此就有了第 2 種方法);
  • 自下而上的迭代;

在《數據結構與算法 JavaScript 描述》中,做者給出了自下而上的迭代方法。

和選擇排序同樣,歸併排序的性能不受輸入數據的影響,但表現比選擇排序好的多,由於始終都是 O(nlogn) 的時間複雜度。代價是須要額外的內存空間。

5.2 算法步驟

  1. 申請空間,使其大小爲兩個已經排序序列之和,該空間用來存放合併後的序列;
  2. 設定兩個指針,最初位置分別爲兩個已經排序序列的起始位置;
  3. 比較兩個指針所指向的元素,選擇相對小的元素放入到合併空間,並移動指針到下一位置;
  4. 重複步驟 3 直到某一指針達到序列尾;
  5. 將另外一序列剩下的全部元素直接複製到合併序列尾。

5.3 動圖演示

5.4 歸併排序代碼示例

//自頂向下的歸併排序
/**
 遞歸使用歸併排序,對array[left...right]的範圍進行排序
 @param array 數組
 @param left 左邊界
 @param right 右邊界
 */
- (void)mergeSortWithArray:(NSMutableArray *)array
                      left:(NSInteger)left
                     right:(NSInteger)right {
    //判斷遞歸到底的狀況
    if (left >= right) {
        //這時候只有一個元素或者是不存在的狀況
        return;
    }
    //中間索引的位置
    NSInteger middle = (right + left) / 2;
    //對 left --- middle 區間的元素進行排序操做
    [self mergeSortWithArray:array left:left right:middle];
    //對 middle + 1 ---- right 區間的元素進行排序操做
    [self mergeSortWithArray:array left:middle + 1 right:right];
    //兩邊排序完成後進行歸併操做
    [self mergeSortWithArray:array left:left middle:middle right:right];
}

/**
 對 [left middle] 和 [middle + 1 right]這兩個區間歸併操做
 @param array 傳入的數組
 @param left 左邊界
 @param middle 中間位置
 @param right 右邊界
 */
- (void)mergeSortWithArray:(NSMutableArray *)array
                      left:(NSInteger)left
                    middle:(NSInteger)middle
                     right:(NSInteger)right {
    //拷貝一個數組出來
    NSMutableArray *copyArray = [NSMutableArray arrayWithCapacity:right - left + 1];
    for (NSInteger i = left; i <= right; i++) {
        //這裏要注意有left的偏移量,因此copyArray賦值的時候要減去left
        copyArray[i - left] = array[i];
    }

    NSInteger i = left, j = middle + 1;
    //循環從left開始到right區間內給數組從新賦值,注意賦值的時候也是從left開始的,不要習慣寫成了從0開始,還有都是閉區間
    for (NSInteger k = left; k <= right; k++) {
        //當左邊界超過中間點時 說明左半部分數組越界了 直接取右邊部分的數組的第一個元素便可
        if (i > middle) {
            //給數組賦值 注意偏移量left 由於這裏是從left開始的
            array[k] = copyArray[j - left];
            //索引++
            j++;
        } else if (j > right) {//當j大於右邊的邊界時證實有半部分數組越界了,直接取左半部分的第一個元素便可
            array[k] = copyArray[i - left];
            //索引++
            i++;
        } else if (copyArray[i - left] > copyArray[j - left]) {//左右兩半部分數組比較
            //當右半部分數組的第一個元素要小時 給數組賦值爲右半部分的第一個元素
            array[k] = copyArray[j - left];
            //右半部分索引加1
            j++;
        } else {//右半部分數組首元素大於左半部分數組首元素
            array[k] = copyArray[i - left];
            i++;
        }
    }
}

6. 希爾排序(shell sort)

6.1 網上文字理解

希爾排序,也稱遞減增量排序算法,是插入排序的一種更高效的改進版本。但希爾排序是非穩定排序算法。

希爾排序是基於插入排序的如下兩點性質而提出改進方法的:

  • 插入排序在對幾乎已經排好序的數據操做時,效率高,便可以達到線性排序的效率;
  • 但插入排序通常來講是低效的,由於插入排序每次只能將數據移動一位;

希爾排序的基本思想是:先將整個待排序的記錄序列分割成爲若干子序列分別進行直接插入排序,待整個序列中的記錄「基本有序」時,再對全體記錄進行依次直接插入排序。

6.2 算法步驟

  1. 選擇一個增量序列 t1,t2,……,tk,其中 ti > tj, tk = 1;
  2. 按增量序列個數 k,對序列進行 k 趟排序;
  3. 每趟排序,根據對應的增量 ti,將待排序列分割成若干長度爲 m 的子序列,分別對各子表進行直接插入排序。僅增量因子爲 1 時,整個序列做爲一個表來處理,表長度即爲整個序列的長度。

6.4 希爾排序代碼示例

- (void)shellAscendingOrderSort:(NSMutableArray *)ascendingArr {
    NSMutableArray *buckt = [self createBucket];
    NSNumber *maxnumber = [self listMaxItem:ascendingArr];
    NSInteger maxLength = numberLength(maxnumber);
    for (int digit = 1; digit <= maxLength; digit++) {
        // 入桶
        for (NSNumber *item in ascendingArr) {
            NSInteger baseNumber = [self fetchBaseNumber:item digit:digit];
            NSMutableArray *mutArray = buckt[baseNumber];
            [mutArray addObject:item];
        }
        NSInteger index = 0;
        for (int i = 0; i < buckt.count; i++) {
            NSMutableArray *array = buckt[i];
            while (array.count != 0) {
                NSNumber *number = [array objectAtIndex:0];
                ascendingArr[index] = number;
                [array removeObjectAtIndex:0];
                index++;
            }
        }
    }
    NSLog(@"希爾升序排序結果:%@", ascendingArr);
}

- (NSMutableArray *)createBucket {
    NSMutableArray *bucket = [NSMutableArray array];
    for (int index = 0; index < 10; index++) {
        NSMutableArray *array = [NSMutableArray array];
        [bucket addObject:array];
    }
    return bucket;
}

- (NSNumber *)listMaxItem:(NSArray *)list {
    NSNumber *maxNumber = list[0];
    for (NSNumber *number in list) {
        if ([maxNumber integerValue] < [number integerValue]) {
            maxNumber = number;
        }
    }
    return maxNumber;
}

NSInteger numberLength(NSNumber *number) {
    NSString *string = [NSString stringWithFormat:@"%ld", (long)[number integerValue]];
    return string.length;
}

- (NSInteger)fetchBaseNumber:(NSNumber *)number digit:(NSInteger)digit {
    if (digit > 0 && digit <= numberLength(number)) {
        NSMutableArray *numbersArray = [NSMutableArray array];
        NSString *string = [NSString stringWithFormat:@"%ld", [number integerValue]];
        for (int index = 0; index < numberLength(number); index++) {
            [numbersArray addObject:[string substringWithRange:NSMakeRange(index, 1)]];
        }
        NSString *str = numbersArray[numbersArray.count - digit];
        return [str integerValue];
    }
    return 0;
}

7. 基數排序(radix sort)

7.1 文字理解

基數排序是一種非比較型整數排序算法,其原理是將整數按位數切割成不一樣的數字,而後按每一個位數分別比較。因爲整數也能夠表達字符串(好比名字或日期)和特定格式的浮點數,因此基數排序也不是隻能使用於整數。

7.2 基數排序 vs 計數排序 vs 桶排序

基數排序有兩種方法:
這三種排序算法都利用了桶的概念,但對桶的使用方法上有明顯差別:

  • 基數排序:根據鍵值的每位數字來分配桶;
  • 計數排序:每一個桶只存儲單一鍵值;
  • 桶排序:每一個桶存儲必定範圍的數值;

7.3 動圖演示

7.4 基數排序代碼示例

- (void)radixAscendingOrderSort:(NSMutableArray *)ascendingArr {
    NSMutableArray *buckt = [self createBucket];
    NSNumber *maxnumber = [self listMaxItem:ascendingArr];
    NSInteger maxLength = numberLength(maxnumber);
    for (int digit = 1; digit <= maxLength; digit++) {
        // 入桶
        for (NSNumber *item in ascendingArr) {
            NSInteger baseNumber = [self fetchBaseNumber:item digit:digit];
            NSMutableArray *mutArray = buckt[baseNumber];
            [mutArray addObject:item];
        }
        NSInteger index = 0;
        for (int i = 0; i < buckt.count; i++) {
            NSMutableArray *array = buckt[i];
            while (array.count != 0) {
                NSNumber *number = [array objectAtIndex:0];
                ascendingArr[index] = number;
                [array removeObjectAtIndex:0];
                index++;
            }
        }
    }
    NSLog(@"基數升序排序結果:%@", ascendingArr);
}

8. 計數排序(counting sort)

8.1 文字理解

計數排序的核心在於將輸入的數據值轉化爲鍵存儲在額外開闢的數組空間中。做爲一種線性時間複雜度的排序,計數排序要求輸入的數據必須是有肯定範圍的整數。

8.2 動圖演示

9. 桶排序(bucket sort)

9.1 文字理解

桶排序是計數排序的升級版。它利用了函數的映射關係,高效與否的關鍵就在於這個映射函數的肯定。爲了使桶排序更加高效,咱們須要作到這兩點:

  1. 在額外空間充足的狀況下,儘可能增大桶的數量
  2. 使用的映射函數可以將輸入的 N 個數據均勻的分配到 K 個桶中

同時,對於桶中元素的排序,選擇何種比較排序算法對於性能的影響相當重要。

9.2 何時最快

當輸入的數據能夠均勻的分配到每個桶中。

9.3 何時最慢

當輸入的數據被分配到了同一個桶中。

若是要搞懂計算機算法,建議仍是多學習實踐,原理搞懂還須要實際去運用

點擊此處,當即與iOS大牛交流學習

相關文章
相關標籤/搜索