因爲面試可能須要手寫算法,網上搜羅了一些資料,整理了下算法的OC的實現代碼,雖然平時開發中通常用不到,可是多積累一些技術知識,仍是對之後發展大有裨益的git
github上搜集的幾大算法原理和實現代碼,只有JavaScript、Python、Go、Java的實現代碼github
相鄰元素進行比較,按照升序或者降序,交換兩個相鄰元素的位置 是一種「穩定排序算法」web
是一種簡單直觀的排序算法。它重複地走訪過要排序的數列,一次比較兩個元素,若是他們的順序錯誤就把他們交換過來。走訪數列的工做是重複地進行直到沒有再須要交換,也就是說該數列已經排序完成。這個算法的名字由來是由於越小的元素會經由交換慢慢「浮」到數列的頂端。
做爲最簡單的排序算法之一,冒泡排序給個人感受就像 Abandon 在單詞書裏出現的感受同樣,每次都在第一頁第一位,因此最熟悉。冒泡排序還有一種優化算法,就是立一個 flag,當在一趟序列遍歷中元素沒有發生交換,則證實該序列已經有序。但這種改進對於提高性能來講並無什麼太大做用。面試
當輸入的數據已是正序時。算法
當輸入的數據是反序時。shell
- (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]; } } } }
快速排序是由東尼·霍爾所發展的一種排序算法。在平均情況下,排序 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) 的歸併排序要小不少。因此,對絕大多數順序性較弱的隨機數列而言,快速排序老是優於歸併排序。
遞歸的最底部情形,是數列的大小是零或一,也就是永遠都已經被排序好了。雖然一直遞歸下去,可是這個算法總會退出,由於在每次的迭代(iteration)中,它至少會把一個元素擺到它最後的位置去。
- (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]; }
它的改進(相比較冒泡算法)在於:先並不急於調換位置,先從A[0]開始逐個檢查,看哪一個數最小就記下該數所在的位置P,等一躺掃描完畢,再把A[P]和A[0]對調,這時A[0]到A[n]中最小的數據就換到了最前面的位置。是一個「不穩定排序算法」
它是一種簡單直觀的排序算法,不管什麼數據進去都是 O(n²) 的時間複雜度。因此用到它的時候,數據規模越小越好。惟一的好處可能就是不佔用額外的內存空間。
- (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]; } } } }
堆排序是指利用堆這種數據結構所設計的一種排序算法。堆積是一個近似徹底二叉樹的結構,並同時知足堆積的性質:即子結點的鍵值或索引老是小於(或者大於)它的父節點。堆排序能夠說是一種利用堆的概念來排序的選擇排序。分爲兩種方法:
- (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); }
插入排序的代碼實現雖然沒有冒泡排序和選擇排序那麼簡單粗暴,但它的原理應該是最容易理解的了,由於只要打過撲克牌的人都應該可以秒懂。插入排序是一種最簡單直觀的排序算法,它的工做原理是經過構建有序序列,對於未排序數據,在已排序序列中從後向前掃描,找到相應位置並插入。
插入排序和冒泡排序同樣,也有一種優化算法,叫作拆半插入。
- (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開發進階交流羣,進羣可自行下載!
歸併排序(Merge sort)是創建在歸併操做上的一種有效的排序算法。該算法是採用分治法(Divide and Conquer)的一個很是典型的應用。
做爲一種典型的分而治之思想的算法應用,歸併排序的實現由兩種方法:
在《數據結構與算法 JavaScript 描述》中,做者給出了自下而上的迭代方法。
和選擇排序同樣,歸併排序的性能不受輸入數據的影響,但表現比選擇排序好的多,由於始終都是 O(nlogn) 的時間複雜度。代價是須要額外的內存空間。
//自頂向下的歸併排序 /** 遞歸使用歸併排序,對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++; } } }
希爾排序,也稱遞減增量排序算法,是插入排序的一種更高效的改進版本。但希爾排序是非穩定排序算法。
希爾排序是基於插入排序的如下兩點性質而提出改進方法的:
希爾排序的基本思想是:先將整個待排序的記錄序列分割成爲若干子序列分別進行直接插入排序,待整個序列中的記錄「基本有序」時,再對全體記錄進行依次直接插入排序。
- (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; }
基數排序是一種非比較型整數排序算法,其原理是將整數按位數切割成不一樣的數字,而後按每一個位數分別比較。因爲整數也能夠表達字符串(好比名字或日期)和特定格式的浮點數,因此基數排序也不是隻能使用於整數。
基數排序有兩種方法:
這三種排序算法都利用了桶的概念,但對桶的使用方法上有明顯差別:
- (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); }
計數排序的核心在於將輸入的數據值轉化爲鍵存儲在額外開闢的數組空間中。做爲一種線性時間複雜度的排序,計數排序要求輸入的數據必須是有肯定範圍的整數。
桶排序是計數排序的升級版。它利用了函數的映射關係,高效與否的關鍵就在於這個映射函數的肯定。爲了使桶排序更加高效,咱們須要作到這兩點:
同時,對於桶中元素的排序,選擇何種比較排序算法對於性能的影響相當重要。
當輸入的數據能夠均勻的分配到每個桶中。
當輸入的數據被分配到了同一個桶中。