Objective-c各種排序算法

Objective-C排序算法

根據將排序記錄是否全部放置在內存中,將排序分為內排序和外排序,之前講的都是內排序,這里總結一下,內排序分為四類:插入排序、交換排序、選擇排序和歸并排序。


image

快排

快速排序是面試中經常會被問的一個排序算法。一般要求手寫。
快排是對冒泡排序的一種改進。
平均時間復雜度O(nlogn),最壞時間復雜度O(n*n)

因為二分查找的時間復雜度是o(logn), 每次分成兩段,那么分的次數就是logn了,每一次處理需要n次計算,那么時間復雜度就是nlogn了!

最壞是O(n^2). 這種情況就是數組剛好的倒序,然后每次去中間元的時候都是取最大或者最小。
穩定性:不穩定。

快速排序時間復雜度為O(n×log(n))的證明


+ (void)quickSort:(NSMutableArray *)arr low:(NSInteger)low high:(NSInteger)high {
    if (arr.count == 0 || low >= high) {
        return;
    }
    
    NSInteger i = low, j = high;
    NSInteger key = [arr[low] integerValue];
    
    while (i < j && [arr[j] integerValue] >= key) {
        j--;
    }
    
    arr[i] = arr[j];
    
    while (i < j && [arr[i] integerValue] <= key) {
        i++;
    }
    
    arr[j] = arr[i];
    
    NSLog(@"一次排序結果:%@", arr);
    
    arr[i] = @(key);
    
    [[self class] quickSort:arr low:low high:i-1];
    [[self class] quickSort:arr low:i+1 high:high];
}

堆排序

堆排序是一種選擇排序,其時間復雜度為O(nlogn)。
定義:

定義

堆的存儲

堆的存儲

其基本思想為(大頂堆):

  1. 將初始待排序關鍵字序列(R1,R2....Rn)構建成大頂堆,此堆為初始的無序區;
  2. 將堆頂元素R[1]與最后一個元素R[n]交換,此時得到新的無序區(R1,R2,......Rn-1)和新的有序區(Rn),且滿足R[1,2...n-1]<=R[n];
  3. 由于交換后新的堆頂R[1]可能違反堆的性質,因此需要對當前無序區(R1,R2,......Rn-1)調整為新堆,然后再次將R[1]與無序區最后一個元素交換,得到新的無序區(R1,R2....Rn-2)和新的有序區(Rn-1,Rn)。不斷重復此過程直到有序區的元素個數為n-1,則整個排序過程完成。

因此對于堆排序,最重要的兩個操作就是構造初始堆和調整堆,其實構造初始堆事實上也是調整堆的過程,只不過構造初始堆是對所有的非葉節點都進行調整。
由于堆也是用數組模擬的,故堆化數組后,第一次將A[0]與A[n - 1]交換,再對A[0…n-2]重新恢復堆。第二次將A[0]與A[n – 2]交換,再對A[0…n - 3]重新恢復堆,重復這樣的操作直到A[0]與A[1]交換。由于每次都是將最大的數據并入到后面的有序區間,故操作完成后整個數組就有序了。

已知一棵完全二叉樹各節點的編號為0到n,如何得出其第一個非葉子節點的編號
最后一個葉子節點的索引值是n-1,它的父節點索引值是[(n-1)-1]/2 = n/2 -1

/* (最大)堆的向下調整算法
  *
  * 注:數組實現的堆中,第N個節點的左孩子的索引值是(2N+1),右孩子的索引是(2N+2)。
  *     其中,N為數組下標索引值,如數組中第1個數對應的N為0。
  *
  * 參數說明:
  *     a -- 待排序的數組
  *     start -- 被下調節點的起始位置(一般為0,表示從第1個開始)
  *     end   -- 截至范圍(一般為數組中最后一個元素的索引)
*/
- (void)heapAdjust:(NSMutableArray *)marr start:(int)start end:(int)end {
    int lchild = 2*start; //i的左孩子節點序號
    int rchild = 2*start+1; //i的右孩子節點序號
    int max = start;  //臨時變量
    
    if (start <= end/2) {  //如果i是葉節點就不用進行調整
        
        if (lchild <= end && [marr[lchild] integerValue] > [marr[max] integerValue]) {
            max = lchild;
        }
        
        if (rchild <= end && [marr[rchild] integerValue] > [marr[max] integerValue] ) {
            max = rchild;
        }
        
        if (max != start) {
            [marr exchangeObjectAtIndex:start withObjectAtIndex:max];
            // 避免調整之后以max為父節點的子樹不是堆
            [self heapAdjust:marr start:max end:end];
        }
    }
}


// 建大根堆
- (void)buildHeap:(NSMutableArray *)marr  {
    int size = (int)marr.count;
    // 最后一個葉子節點的索引值是n-1,它的父節點索引值是[(n-1)-1]/2 = n/2 -1
    for (int i = size/2-1; i >= 0; --i) {
        [self heapAdjust:marr start:i end:size-1];
    }
}

- (void)heapSort:(NSMutableArray *)marr {
    [self buildHeap:marr];
    
    NSLog(@"build after: %@", marr);
    
    int len = (int)marr.count-1;
    for (int i = len; i >= 0; i--) {
        // 交換堆頂和最后一個元素,即每次將剩余元素中的最大者放到最后面
        [marr exchangeObjectAtIndex:0 withObjectAtIndex:i];
        // 重新調整堆頂節點成為大頂堆
        [self heapAdjust:marr start:0 end:i-1];
    }
}



int main(int argc, const char * argv[]) {
    @autoreleasepool {
        NSArray *arr = @[@(12), @(14), @(100),  @(29), @(8), @(21), @(15), @(59), @(50), @(99)];
        NSMutableArray *marr = [NSMutableArray arrayWithArray:arr];
        SGSort *sort = [[SGSort alloc] init];
        [sort heapSort:marr];
        NSLog(@"heapsort result =%@", marr);
    }
    return 0;
}

// 輸出結果
2017-08-27 19:55:04.657 SuanFaXueXi[3487:106882] build after: (
    100,
    99,
    50,
    59,
    14,
    21,
    15,
    29,
    12,
    8
)
2017-08-27 19:55:07.151 SuanFaXueXi[3487:106882] heapsort result =(
    8,
    12,
    14,
    15,
    21,
    29,
    50,
    59,
    99,
    100
)

由于每次重新恢復堆的時間復雜度為O(logN),共N - 1次重新恢復堆操作,再加上前面建立堆時N / 2次向下調整,每次調整時間復雜度也為O(logN)。二次操作時間相加還是O(N * logN)。故堆排序的時間復雜度為O(N * logN)

堆排序適合于數據量非常大的場合(百萬數據)。
堆排序不需要大量的遞歸或者多維的暫存數組。這對于數據量非常巨大的序列是合適的。比如超過數百萬條記錄,因為快速排序,歸并排序都使用遞歸來設計算法,在數據量非常大的時候,可能會發生堆棧溢出錯誤。

歸并排序

希爾排序

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

計數排序

如果在面試中有面試官要求你寫一個O(n)時間復雜度的排序算法,你千萬不要立刻說:這不可能!雖然前面基于比較的排序的下限是O(nlogn)。但是確實也有線性時間復雜度的排序,只不過有前提條件,就是待排序的數要滿足一定的范圍的整數,而且計數排序需要比較多的輔助空間。其基本思想是,用待排序的數作為計數數組的下標,統計每個數字的個數。然后依次輸出即可得到有序序列。
 
今天學習了計數排序,貌似計數排序的復雜度為o(n)。很強大。他的基本思路為:

  1. 我們希望能線性的時間復雜度排序,如果一個一個比較,顯然是不實際的,書上也在決策樹模型中論證了,比較排序的情況為nlogn的復雜度。

  2. 既然不能一個一個比較,我們想到一個辦法,就是如果我在排序的時候就知道他的位置,那不就是掃描一遍,把他放入他應該的位置不就可以了嘛。

3.要知道他的位置,我們只需要知道有多少不大于他不就可以了嗎?

  1. 以此為出發點,我們怎么確定不大于他的個數呢?我們先來個約定,如果數組中的元素都比較集中,都在[0, max]范圍內。我們開一個max的空間b數組,把b數組下標對應的元素和要排序的A數組下標對應起來。這樣不就可以知道不比他大的有多少個了嗎?我們只要把比他小的位置元素個數求和,就是不比他大的。例如:A={3,5,7};我們開一個大小為8的數組b,把a[0] = 3 放入b[3]中,使b[3] = 0; 同理 b[5] = 1; b[7] = 2;其他我們都設置為-1,哈哈我們只需要遍歷一下b數組,如果他有數據,就來出來,鐵定是當前最小的。如果要知道比a[2]小的數字有多少個,值只需要求出b[0] – b[6]的有數據的和就可以了。這個0(n)的速度不是蓋得。

  2. 思路就是這樣咯。但是要注意兩個數相同的情況A = {1,2,3,3,4},這種情況就不可以咯,所以還是有點小技巧的。

6.處理小技巧:我們不把A的元素大小與B的下標一一對應,而是在B數組對應處記錄該元素大小的個數。這不久解決了嗎。哈哈。例如A = {1,2,3,3,4}我們開大小為5的數組b;記錄數組A中元素值為0的個數為b[0] = 0, 記錄數組A中元素個數為1的b[1] = 1,同理b[2] = 1, b[3] = 2, b[4] = 1;好了,這樣我們就知道比A4小的元素個數是多少了:count = b[0] + b[1] + b[2] + b[3] = 4;他就把A[4]的元素放在第4個位置。

算法系列:計數排序

桶排序

基數排序

總結

在前面的介紹和分析中我們提到了冒泡排序、選擇排序、插入排序三種簡單的排序及其變種快速排序、堆排序、希爾排序三種比較高效的排序。后面我們又分析了基于分治遞歸思想的歸并排序還有計數排序、桶排序、基數排序三種線性排序。我們可以知道排序算法要么簡單有效,要么是利用簡單排序的特點加以改進,要么是以空間換取時間在特定情況下的高效排序。
  



1. 從平均時間來看,快速排序是效率最高的,但快速排序在最壞情況下的時間性能不如堆排序和歸并排序。而后者相比較的結果是,在n較大時歸并排序使用時間較少,但使用輔助空間較多。

2. 上面說的簡單排序包括除希爾排序之外的所有冒泡排序、插入排序、簡單選擇排序。其中直接插入排序最簡單,但序列基本有序或者n較小時,直接插入排序是好的方法,因此常將它和其他的排序方法,如快速排序、歸并排序等結合在一起使用。

3. 基數排序的時間復雜度也可以寫成O(d*n)。因此它最使用于n值很大而關鍵字較小的的序列。若關鍵字也很大,而序列中大多數記錄的最高關鍵字均不同,則亦可先按最高關鍵字不同,將序列分成若干小的子序列,而后進行直接插入排序。

4. 從方法的穩定性來比較,基數排序是穩定的內排方法,所有時間復雜度為O(n^2)的簡單排序也是穩定的。但是快速排序、堆排序、希爾排序等時間性能較好的排序方法都是不穩定的。穩定性需要根據具體需求選擇。

5. 上面的算法實現大多數是使用線性存儲結構,像插入排序這種算法用鏈表實現更好,省去了移動元素的時間。具體的存儲結構在具體的實現版本中也是不同的。

參考

排序 Heap Sort
堆排序算法解析
堆排序
面試中的排序算法總結
計數排序,傳說時間復雜度為0(n)的排序

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • 概述:排序有內部排序和外部排序,內部排序是數據記錄在內存中進行排序,而外部排序是因排序的數據很大,一次不能容納全部...
    每天刷兩次牙閱讀 3,743評論 0 15
  • 概述 排序有內部排序和外部排序,內部排序是數據記錄在內存中進行排序,而外部排序是因排序的數據很大,一次不能容納全部...
    蟻前閱讀 5,220評論 0 52
  • 概述排序有內部排序和外部排序,內部排序是數據記錄在內存中進行排序,而外部排序是因排序的數據很大,一次不能容納全部的...
    Luc_閱讀 2,300評論 0 35
  • statusBar 20像素,撥打手機時為40像素 navigationBar 44像素 未完待續
    NapoleonY閱讀 215評論 0 0