目錄
一、總概
先放兩張圖,記是記不住,這輩子...不,一個一個去深入研究學習是能記住的。
常見的算法時間複雜度由小到大依次爲:
Ο(1)<Ο(log2n)<Ο(n)<Ο(nlog2n)<Ο(n²)<Ο(n³)<…<Ο(2^n)<Ο(n!)<O(n^n)
計算時間複雜度
⑴ 找出算法中的基本語句;
算法中執行次數最多的那條語句就是基本語句,通常是最內層循環的循環體。
⑵ 計算基本語句的執行次數的數量級;
只需保留f(n)中的最高次冪正確即可,可以忽略所有低次冪和最高次冪的係數。
⑶ 用大Ο記號表示算法的時間性能。
將基本語句執行次數的數量級放入大Ο記號中。
- 如果算法中包含嵌套的循環,則基本語句通常是最內層的循環體,如果算法中包含並列的循環,則將並列循環的時間複雜度相加。
- 如:第一個for循環的時間複雜度爲Ο(n),第二個for循環的時間複雜度爲Ο(n²),則整個算法的時間複雜度爲Ο(n+n²)=Ο(n²)。
- 注:加法原則:T(n)=O(f(n))+O(g(n))=O(max(fn,gn))
Ο(1)表示基本語句的執行次數是一個常數,一般來說,只要算法中不存在循環語句,其時間複雜度就是Ο(1)。Ο(log2n)、Ο(n)、Ο(nlog2n)、Ο(n2)和Ο(n3)稱爲多項式時間,而Ο(2n)和Ο(n!)稱爲指數時間。計算機科學家普遍認爲前者是有效算法,把這類問題稱爲P類問題,而把後者稱爲NP問題。
- 對於一個循環,假設循環體的時間複雜度爲 O(n),循環次數爲 m,則這個循環的時間複雜度爲 O(n×m)。此時時間複雜度爲 O(n × 1),即 O(n)。
- 對於多個循環,假設循環體的時間複雜度爲 O(n),各個循環的循環次數分別是a, b, c...,則這個循環的時間複雜度爲 O(n×a×b×c...)。由裏向外分析這些循環的次數,此時時間複雜度爲 O(n × n × 1),即 O(n²)。
- 對於順序執行的語句或者算法,總的時間複雜度等於其中最大的時間複雜度。此時時間複雜度爲 max(O(n²), O(n)),即 O(n²)。
- 對於條件判斷語句,總的時間複雜度等於其中 時間複雜度最大的路徑 的時間複雜度。此時時間複雜度爲 max(O(n^2), O(n)),即 O(n^2)。
基本策略是:從內向外分析,從最深層開始分析。如果遇到函數調用,要深入函數進行分析。
算法穩定性:相同元素的前後順序在任何情況都不會發生改變,這種排序成爲穩定排序算法。反之成爲不穩定排序算法。
二、詳解代碼
代碼一時半會沒看懂,可以看看書《數據結構》C語言版,看看視頻,研究的過程印象更深,理解更深,更有成就感。
思路懂了但是代碼還是不會寫的可以看看視頻:歸併排序、堆排序。
代碼方案不是唯一的,生活也是。
插入排序 : 直接插入排序 & 希爾排序
-直接插入排序
#pragma mark 直接插入排序
-(void)sortByInsertWithArray:(NSMutableArray *)array{
/**
算法步驟:
1. 從第一個元素開始,認爲該元素已經是排好序的。
2. 取下一個元素,在已經排好序的元素序列中從後向前掃描。
3. 如果已經排好序的序列中元素大於新元素,則將該元素往右移動一個位置。
4. 重複步驟3,直到已排好序的元素小於或等於新元素。
5. 在當前位置插入新元素。
6. 重複步驟2。
複雜度:
平均時間複雜度:O(n^2)
平均空間複雜度:O(1)
*/
//外層for循環從第二個元素開始,i = 1 而不是 0 。有n個數字就要執行n-1次
for (NSInteger i = 1; i < array.count; i ++) {
//待排元素
NSInteger temp = [array[i] integerValue];
//從後向前掃描,與 待排元素 比較。
//待排元素 大於 j元素,不做處理
//待排元素 小於 j元素,j元素 後移,待排元素補上j位置。
for (NSInteger j = i - 1; j >= 0 ; j --) {
if (temp < [array[j] integerValue]) {
array[j + 1] = array[j];
array[j] = [NSNumber numberWithInteger:temp];
}
}
}
}
-希爾排序
#pragma mark 希爾排序
- (void)sortByShellWithArray:(NSMutableArray *)array{
/**
希爾排序(Shell Sort)也稱遞減增量排序算法,是插入排序的一種更高效的改進版本。但希爾排序是非穩定排序算法。
希爾排序是基於插入排序的以下兩點性質而提出改進方法的:
插入排序在對幾乎已經排好序的數據操作時, 效率高, 即可以達到線性排序的效率
但插入排序一般來說是低效的, 因爲插入排序每次只能將數據移動一位
希爾排序的基本思想是:先將整個待排序的記錄序列分割成爲若干子序列分別進行直接插入排序,待整個序列中的記錄“基本有序”時,再對全體記錄進行依次直接插入排序。
希爾排序是把記錄按下標的一定增量分組,對每組使用直接插入排序算法排序;隨着增量逐漸減少,每組包含的關鍵詞越來越多,當增量減至1時,整個文件恰被分成一組,算法便終止。
希爾排序在插入排序的基礎上增加一個叫增量的概念。那什麼增量?插入排序只能與相鄰的元素進行比較,而希爾排序則是進行跳躍比較,而增量就是步長。比如增量爲3時,下標爲0的元素與下標爲3的元素比較,3再與6比較,1與4比較,4再與7比較……比較完後,再去減少增量,重複之前步驟,直到增量爲1,此時只有一個分組了,再對這一個分組進行插入排序,整個希爾排序就結束了。
算法步驟:
1. 選擇一個增量序列t1,t2,…,tk,其中ti>tj,tk=1;
2. 按增量序列個數k,對序列進行k 趟排序;
3. 每趟排序,根據對應的增量ti,將待排序列分割成若干長度爲m 的子序列,分別對各子表進行直接插入排序。僅增量因子爲1 時,整個序列作爲一個表來處理,表長度即爲整個序列的長度。
用這樣增量序列的希爾排序比插入排序要快,甚至在小數組中比快速排序和堆排序還快,但是在涉及大量數據時希爾排序還是比快速排序慢。
希爾排序複雜度分析:
在最壞的情況下時間複雜度仍爲O(n²),而使用最優的增量在最壞的情況下卻爲O(n²⁄³)。需要注意的是,增量序列的最後一個增量值必須等於1纔行。另外由於記錄是跳躍式的移動,希爾排序並不是一種穩定的排序算法。
*/
int N = (int)array.count;
//設置增量,每次遞減 /2
for (int gap= N / 2; gap > 0; gap /= 2) {
//直接進行一個插入排序
[self sortByInsertWithArray:array andGap:gap];
}
}
-(NSMutableArray *)sortByInsertWithArray:(NSMutableArray *)array andGap:(NSInteger)gap{
//循環次數,(array.count - gap )趟
for (NSInteger i = gap; i < array.count; i ++) {
NSInteger temp = [array[i] integerValue];
//把當前元素插入到前面去
for (NSInteger j = i - gap ; j >= 0 ; j -= gap) {
if (temp < [array[j] integerValue]) {
array[j + gap] = array[j];
array[j] = [NSNumber numberWithInteger:temp];
}
}
}
return array;
}
選擇排序 : 直接選擇排序 & 堆排序
-直接選擇排序
#pragma mark 直接選擇排序
-(void)sortBySelectWithArray:(NSMutableArray *)array{
//第一個元素和剩餘的元素比較,如果其他的元素小,就交換。
//第二個元素和剩餘的元素比較,如果其他的元素小,就交換。
//重複下去
//不穩定,平均時間複雜度 O(n^2)
int i , j , k ;
//有多少個數字就要執行多少次
for (i = 0; i <= array.count - 1; i++) {
k = i;
//第i個元素和餘下的比較
for (j = i + 1; j <= array.count - 1; j++) {
//如果餘下的某個元素比第i個元素小,記住下標
if ([array[k] integerValue] > [array[j] integerValue]) {
k = j;
}
}
//開始交換
if (k != i) {
id temp = array[i];
array[i] = array[k];
array[k] = temp;
}
}
}
-堆排序
#pragma mark 堆排序
-(void)sortByHeapWithArray:(NSMutableArray *)array{
/**
堆排序(Heap Sort)是指利用堆這種數據結構所設計的一種排序算法。
堆積是一個近似完全二叉樹的結構,並同時滿足堆積的性質:即子結點的鍵值或索引總是小於(或者大於)它的父節點。
堆排序的平均時間複雜度爲Ο(nlogn) 。
一般都用數組來表示堆,i結點的父結點下標就爲(i – 1) / 2。它的左右子結點下標分別爲2 * i + 1和2 * i + 2。
如第0個結點左右子結點下標分別爲1和2。
堆的添加從A[n](數組尾)開始,再進行恢復堆次序;刪除從A[0](數組首)開始,由A[n]補到A[0]位,再進行恢復堆次序。
堆排序過程的時間複雜度是O(nlgn)。因爲建堆的時間複雜度是O(n)(調用一次);調整堆的時間複雜度是lgn,調用了n-1次,所以堆排序的時間複雜度是O(nlgn)。
算法步驟:
1. 創建一個堆H[0..n-1]
2. 把堆首(最大值)和堆尾互換
3. 把堆的尺寸縮小1,並調用shift_down(0),目的是把新的數組頂端數據調整到相應位置
4. 重複步驟2,直到堆的尺寸爲1
*/
//創建一個堆
int n =(int) array.count;
[self creatHeapWithArray:array total:n];
//排序
for (int i = n - 1; i >= 0; i--) {
//交換第一個和最後一個元素
id temp = array[0];
array[0] = array[i];
array[i] = temp;
//恢復堆次序
[self heapAdjustWithArray:array startIndex:0 total:i];
}
}
/// 創建一個堆
/// @param array 數組(樹)
/// @param total 總節點數
-(void)creatHeapWithArray:(NSMutableArray *)array total:(int)total{
/**
創建一個堆,從最後一個元素的父節點開始,依次向上
*/
int last_node = total - 1; //最後一個節點
int parent_last = (last_node - 1) / 2; //最後一個節點的父節點
for (int i = parent_last; i >= 0; i--) {
[self heapAdjustWithArray:array startIndex:i total:total];
}
}
/// 對某個節點進行 heapify 堆次序
/// @param array 數組(代表樹)
/// @param startIndex 開始節點(對那個點進行heapify)
/// @param total 總的節點數
-(void)heapAdjustWithArray:(NSMutableArray *)array startIndex:(int)startIndex total:(int)total{
//遞歸出口
if (startIndex >= total) return;
int leftIndex = startIndex * 2 + 1; //左子樹下標
int rightIndex = startIndex * 2 + 2; //右子樹下標
int maxIndex = startIndex; //假設最大下標
//如果 左子樹元素 比 根節點元素大,並且 判斷子樹未出界
if (leftIndex < total && [array[leftIndex] intValue] > [array[maxIndex] intValue]) {
maxIndex = leftIndex;
}
if (rightIndex < total && [array[rightIndex] intValue] > [array[maxIndex] intValue]) {
maxIndex = rightIndex;
}
if (maxIndex != startIndex) {
//進行交換
id temp = array[startIndex];
array[startIndex] = array[maxIndex];
array[maxIndex] = temp;
//遞歸
[self heapAdjustWithArray:array startIndex:maxIndex total:total];
}
}
交換排序 : 冒泡排序 & 快速排序
-冒泡排序
#pragma mark 冒泡排序
-(void)sortByBubbleWithArray:(NSMutableArray *)array{
//交換相鄰的2個元素,值大的必然會交換到末尾
//穩定,平均時間複雜度 O(n^2)
//有 array.count -1 趟交換
for (int i = 0; i< array.count -1 ; i++) {
//內層for循環控制 每趟的 交換次數, 每次遞減
for (int j = 0; j< array.count -1 - i; j++) {
if ([array[j] integerValue] > [array[j + 1] integerValue]) {
[array exchangeObjectAtIndex:j withObjectAtIndex:j+1];
}
}
}
}
-快速排序
#pragma mark 快速排序
-(void)sortByQuickWithArray:(NSMutableArray *)array low:(int )low high:(int )high{
/**
實現思路:
1. 從數列中挑出一個元素,稱爲 "基準"(pivot).
2. 重新排序數列,所有元素比基準值小的擺放在基準前面,所有元素比基準值大的擺在基準的後面(相同的數可以到任一邊)。
在這個分割之後,該基準是它的最後位置。這個稱爲分割(partition)操作。
3. 遞歸地(recursive)把小於基準值元素的子數列和大於基準值元素的子數列排序。
快速排序是基於分治模式處理的,對一個典型子數組A[p...r]排序的分治過程爲三個步驟:
1.分解:
A[p..r]被劃分爲倆個(可能空)的子數組A[p ..q-1]和A[q+1 ..r],使得
A[p ..q-1] <= A[q] <= A[q+1 ..r]
2.解決:通過遞歸調用快速排序,對子數組A[p ..q-1]和A[q+1 ..r]排序。
3.合併。
遞迴的最底部情形,是數列的大小是零或一,也就是永遠都已經被排序好了。雖然一直遞迴下去,但是這個算法總會結束,因爲在每次的迭代(iteration)中,它至少會把一個元素擺到它最後的位置去。
複雜度:
平均時間複雜度:O(n^2)
平均空間複雜度:O(nlogn) O(nlogn)~O(n^2)
*/
if (low >= high) {
return;
}
int i = low;
int j = high;
int pivotKey = [array[i] intValue];
while (i < j) {
//順序很重要,先從右邊判斷, 找到比基數大的
while (i < j && [array[j] intValue] >= pivotKey) {
j--;
}
//從左邊判斷, 找到比基數小的
while (i < j && [array[i] intValue] <= pivotKey) {
i++;
}
//找到之後交換
if (i<j) {
id temp = array[i];
array[i] = array[j];
array[j] = temp;
}
}
//最後將基準數放到正確位置
array[low] = array[i];
array[i] = [NSNumber numberWithInt:pivotKey];
//分別遞歸左邊右邊
[self sortByQuickWithArray:array low:low high:i-1];
[self sortByQuickWithArray:array low:i +1 high:high];
}
基數排序
#pragma mark - 基數排序
-(void)sortByRadixWithArray:(NSMutableArray *)array{
/**
1.首先根據個位數的數值,在遍歷數據時將它們各自分配到編號0至9的桶(個位數值與桶號一一對應)中.
2.將所有桶中所盛數據按照桶號由小到大依次重新收集串起來,得到如下仍然無序的數據序列.
3.再進行一次分配,這次根據十位數值來分配(同上).
4.重複進行以上的動作直至最高位數爲止.
時間複雜度
假設在基數排序中,r爲基數,d爲位數。則基數排序的時間複雜度爲O(d(n+r))。我們可以看出,基數排序的效率和初始序列是否有序沒有關聯。
空間複雜度
在基數排序過程中,對於任何位數上的基數進行“裝桶”操作時,都需要n+r個臨時空間。
算法穩定性
是按照低位先排序,然後收集;再按照高位排序,然後再收集;依次類推,直到最高位。有時候有些屬性是有優先級順序的,先按低優先級排序,再按高優先級排序。最後的次序就是高優先級高的在前,高優先級相同的低優先級高的在前。基數排序基於分別排序,分別收集,所以是穩定的。
作者:SkyMing一C
鏈接:https://www.jianshu.com/p/43de49cd23e6
來源:簡書
著作權歸作者所有。商業轉載請聯繫作者獲得授權,非商業轉載請註明出處。
*/
// 1.創建10個桶數組
NSMutableArray *buckt = [self createBucket];
// 2.找到數組最大的元素
NSNumber *maxnumber = [self listMaxItem:array];
// 3.最大元素的位數
NSInteger maxLength = [self numberLength:maxnumber];
// digit 位數
for (int digit = 1; digit <= maxLength; digit++) {
// 根據 獲取數字的最後一位的值 入桶
for (NSNumber *item in array) {
//數字的最後一位的值
NSInteger baseNumber = [self fetchLastValueWithNumber:item digit:digit];
NSMutableArray *mutArray = buckt[baseNumber];
[mutArray addObject:item];
}
//所有數組歸一
NSInteger index = 0;
for (int i = 0; i < buckt.count; i++) {
NSMutableArray *tempArray = buckt[i];
while (tempArray.count != 0) {
NSNumber *number = [tempArray objectAtIndex:0];
array[index] = number;
[tempArray removeObjectAtIndex:0];
index++;
}
}
}
}
/// 創建多個桶數組
- (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)fetchLastValueWithNumber:(NSNumber *)number digit:(NSInteger)digit {
if (digit > 0 && digit <= [self numberLength:number]) {
NSMutableArray *numbersArray = [NSMutableArray array];
NSString *string = [NSString stringWithFormat:@"%ld", [number integerValue]];
//將數字分割成數組
for (int index = 0; index < [self numberLength:number]; index++) {
[numbersArray addObject:[string substringWithRange:NSMakeRange(index, 1)]];
}
//獲取最後一位的元素值
NSString *str = numbersArray[numbersArray.count - digit];
return [str integerValue];
}
return 0;
}
歸併排序
#pragma mark - 歸併排序
-(void)sortByMegerWithArray:(NSMutableArray *)array{
/**
把序列分成元素儘可能相等的兩半。
把兩半元素分別進行排序。
把兩個有序表合併成一個。
*/
int L = 0;
int R = (int)array.count - 1;
[self sortByMeger:array leftIndex:L rightIndex:R];
}
/// 歸併排序,無序數組
/// @param array 待排數組
/// @param leftIndex 起始下標
/// @param rightIndex 結束下標
-(void)sortByMeger:(NSMutableArray *)array leftIndex:(int)leftIndex rightIndex:(int)rightIndex{
//跳出遞歸
if (leftIndex == rightIndex) {
return;
}else{
int midIndex = (rightIndex + leftIndex ) / 2;
//遞歸 排序左側
[self sortByMeger:array leftIndex:leftIndex rightIndex:midIndex];
//遞歸 排序右側 midIndex+1 :保證取同一個中間下標
[self sortByMeger:array leftIndex:midIndex+1 rightIndex:rightIndex];
// 左側 和 右側 有序,歸併
[self meger:array leftIndex:leftIndex midIndex:midIndex+1 rightIndex:rightIndex];
}
}
/// 一次歸併排序,假設從中間分開左右數組就有序
/// @param array 待排數組
/// @param leftIndex 起始下標
/// @param midIndex 中間下標
/// @param rightIndex 結束下標
-(void)meger:(NSMutableArray *)array leftIndex:(int)leftIndex midIndex:(int)midIndex rightIndex:(int)rightIndex{
int left_size = midIndex - leftIndex;
int right_size = rightIndex - midIndex + 1;
NSMutableArray *leftArr = [NSMutableArray array];
NSMutableArray *rightArr = [NSMutableArray array];
//分成2組,添加到left數組
for (int i = leftIndex; i < midIndex; i++) {
[leftArr addObject:array[i]];
}
//分成2組,添加到right數組
for (int i = midIndex; i <= rightIndex; i++) {
[rightArr addObject:array[i]];
}
int i = 0, j = 0 , k = leftIndex; //i指向leftArr,j指向rightArr,k指向array
//比較left數組 和 right數組的每一個元素,依次添加到array中
while (i < left_size && j < right_size) {
if ([leftArr[i] intValue] < [rightArr[j] intValue]) {
array[k] = leftArr[i];
i++;
k++;
}else{
array[k] = rightArr[j];
j++;
k++;
}
}
//比較完,某一個數組有剩餘的直接添加到array中
while (i < left_size) {
array[k] = leftArr[i];
i++;
k++;
}
while (j < right_size) {
array[k] = rightArr[j];
j++;
k++;
}
}
/// 合併2個有序的數組
/// @param array 結果數組
/// @param leftArr 有序的待合併數組leftArr
/// @param rightArr 有序的待合併數組rightArr
-(void)sortByMeger:(NSMutableArray *)array leftArr:(NSMutableArray *)leftArr rightArr:(NSMutableArray *)rightArr{
int left_size = (int)leftArr.count;
int right_size = (int)rightArr.count;
int i = 0, j = 0 , k = 0; //i指向leftArr,j指向rightArr,k指向array
//比較left數組 和 right數組的每一個元素,依次添加到array中
while (i < left_size && j < right_size) {
if ([leftArr[i] intValue] < [rightArr[j] intValue]) {
array[k] = leftArr[i];
i++;
k++;
}else{
array[k] = rightArr[j];
j++;
k++;
}
}
//比較完,某一個數組有剩餘的直接添加到array中
while (i < left_size) {
array[k] = leftArr[i];
i++;
k++;
}
while (j < right_size) {
array[k] = rightArr[j];
j++;
k++;
}
NSLog(@"合併2個有序的數組:- %@",array);
}