作者:ZhengYaWei
連結:https://www.jianshu.com/p/ceed2daebc47
前言
該篇文章主要會涉及如下幾個問題:
1、if else 和 switch case 在日常開發中該如何抉擇?兩者相比誰的效率會高些?
2、如何基於赫夫曼樹結構減少 if else 分支判斷次數?
3、如何巧妙的應用 do…while(0) 改善程式碼結構?
4、哨兵是什麼東西?如何利用哨兵提高有序陣列查詢效率?
5、如何降低 for 迴圈巢狀的時間複雜度?
6、如何利用策略樣式替換繁瑣的 if else 分支?
雖然該篇文章說來說去都是 if else、switch case、for、while幾個很簡單的關鍵字,但是確實都是一些比較實用的小技巧,希望對你實際工作有所幫助。
PS: 做了回標題黨,見諒?????
一、if else 和 switch case 效率問題
switch case 與 if else 的根本區別在於: switch case 會生成一個跳轉表來指示實際的 case 分支的地址,而這個跳轉表的索引號與switch變數的值是相等的。從而,switch case 不用像 if else 那樣遍歷條件分支直到命中條件,而只需訪問對應索引號的表項從而到達定位分支的目的。switch case 會生成一份大小(表項數)為最大 case 常量 +1 的跳錶,程式首先判斷 switch 變數是否大於最大 case 常量,若大於,則跳到 default 分支處理;否則取得索引號為switch變數大小的跳錶項的地址(即跳錶的起始地址+表項大小*索引號),程式接著跳到此地址執行,到此完成了分支的跳轉。
switch 的缺點主要有兩點: 1、 switch 有點以空間換時間的意思,因為它要生成跳錶,特別是當case常量分佈範圍很大但實際有效值又比較少的情況,switch case 的空間利用率將變得很低。2、if else 能應用於更多的場合,switch case可能就做不來,如:if (a > 1) 。
除此, if else 的效率問題同簡單檔案壓縮原理還有一定的關聯,主要是涉及赫夫曼樹結構知識,具體可以參照筆者之前寫的這篇文章。
二、用do while(0) 改善程式碼結構
先看一段程式碼,要重點註意程式碼中的註釋。
- (NSString *)handleString:(NSString *)str {
if (![str isKindOfClass:[NSString class]]) {
return nil;
}
if(str.length <= 0) {
return nil;
}
// 第一部分邏輯依賴於前面的判斷,只有判斷透過的時候才執行
我是第一部分邏輯偽程式碼
// 第二部分邏輯不依賴於前面的判斷(第二部分中的邏輯可能會依賴第一部分邏輯處理結果),無論判斷是否透過都要執行
我是第二部分邏輯偽程式碼
}
試問,怎樣做才能巧妙的滿足上述註釋程式碼的需求,因為上述程式碼中存在 return nil; 一旦執行到此處,邏輯一和邏輯二處的偽程式碼都不會再執行。為了滿足上述要求,我們可以巧妙的利用 break 退出臨時構造的程式碼塊,但不退出整個函式。
- (NSString *)handleString:(NSString *)str {
do {
if (![str isKindOfClass:[NSString class]]) {
break;
}
if(str.length <= 0) {
break;
}
// 第一部分邏輯依賴於前面的判斷,只有判斷透過的時候才執行
我是第一部分邏輯偽程式碼
}while (0);
// 第二部分邏輯不依賴於前面的判斷(第二部分中的邏輯可能會依賴第一部分邏輯處理結果),無論判斷是否透過都要執行
我是第二部分邏輯偽程式碼
}
三、有序陣列查詢操作中的哨兵
正常的查詢處理。
NSArray *arr = @[@1,@2,@3,@4,@5];
for (NSInteger i = 0; i if ([arr[i] integerValue] == 2) {
NSLog(@"for 找到了");
}
}
利用哨兵進行查詢處理。
- (BOOL)search:(NSNumber *)key array:(NSMutableArray *)arr{
if (arr.count <= 0) {
return NO;
}
NSInteger i = arr.count - 1;
NSNumber *firstObj = (NSNumber *)arr[0];
if ([firstObj integerValue] == [key integerValue]) {
return 0;
}
NSLock * lock = [[NSLock alloc]init];
[lock lock];
arr[0] = key;
//同上面for迴圈相比,i
while ([arr[i] integerValue] != [key integerValue]) {//該句程式碼和上面for迴圈中的if判斷等價======
i--;//該句程式碼和上面的i+等價======
}
arr[0] = firstObj;
[lock unlock];
if (i == 0) {
return NO;
}else{
return YES;
}
}
仔細觀察上述兩段程式碼,同樣是在有序陣列中查詢標的為 2 的元素,第一段程式碼是常規迭代處理,第二段程式碼是將要查詢的元素設定為哨兵。同第一段程式碼相比第二種方式少了 i < arr.count 的判斷,在小批次有序陣列查詢中對效率的提升並無明顯影響,但是在處理大批次資料時候,對效能提升還是比較明顯的。
四、多層 for 巢狀處理
實際開發中應儘量避免使用雙層 for 迴圈,客戶端資料量比較小可能實際開發中並不是很註意這些。但是後端開發過程中,資料量比較大, 為了提升效能,有些公司後端開發中可能會直接規定避免使用多層 for 迴圈巢狀的形式。一般第二層或更深層的 for 迴圈可以使用字典替換。雙層 for 迴圈巢狀的時間複雜度是 n 的二次方。但如果內部 for 迴圈用字典代替時間複雜度為 O(2n)( 實際是 O(n))。如: 兩個陣列中有且只有一個相同元素,尋找該元素。其中一個陣列就可以先用字典做儲存,遍歷第一個陣列的時候, 同字典中的資料做比較即可。
NSArray *arr1 = @[@1,@2,@3,@4,@5];
NSArray *arr2 =@[@5,@6,@7,@8];
NSMutableDictionary *dict = [NSMutableDictionary dictionary];
for (NSInteger i = 0; i [dict setObject:arr2[i] forKey:[NSString stringWithFormat:@"%ld",i]];
}
for (NSInteger i= 0 ; i NSNumber *number = [dict objectForKey:[NSString stringWithFormat:@"%ld",i]];
if ([arr1[i] integerValue] == [number integerValue]) {
NSLog(@"相同的資料為:%@",number);
break;
}
}
五、用策略樣式替換 if else
筆者之前這篇文章的第四部分有詳細介紹到,這裡不再做過多描述。
https://www.jianshu.com/p/98fa80eebc52
小結
文章很簡短,但是筆者自認為都是一些很實用的技巧。可能因為if else、switch、while、for 這幾個關鍵字過於簡單,許多開發者並不太註意,也不知道這些技巧。