‘算法空間復雜度’,別以為這個東西多么高大上,我保證你看完這篇文章就能明白。
最近在啃算法,發現非常有趣。在我學習的過程中發現了一個問題,那就是空間復雜度的問題,它絕對是效率的殺手。
關於空間復雜度的介紹(摘自百度)
空間復雜度(Space Complexity)是對一個算法在運行過程中臨時占用存儲空間大小的量度,記做S(n)=O(f(n))。比如直接插入排序的時間復雜度是O(n^2),空間復雜度是O(1) 。而一般的遞歸算法就要有O(n)的空間復雜度了,因為每次遞歸都要存儲返回信息。一個算法的優劣主要從算法的執行時間和所需要占用的存儲空間兩個方面衡量。
拿插入排序來說。插入排序和我們現實中打撲克牌時理清牌的那一步很像。拿斗地主來說,我們經常會把順子理出來,回想下我們是怎么理的?比如我們有這么5張牌9、8、10、7、6。過程應該是這樣的:
9、8、10、7、6
從8開始,8發現9比它大,然后8插到9前面。
8、9、10、7、6
然后到10,10發現它比前面2個都大,所以不用動。
8、9、10、7、6
然后到7,7發現10比它大,然后跟10換位置。
8、9、7、10、6
然后7又發現9也比它大,於是又跟9換位置
8、7、9、10、6
然后7又發現8也比它大,於是又跟8換位置
7、8、9、10、6
等等,好像有點不對。到牌‘7’的那一步好像太麻煩了,我們平時是把7拿起來,直接插到8前面就完事了。簡單快捷,絕對比一個個插要快。沒錯!這就是空間復雜度的問題。下面直接上2組代碼來校驗一下。
public static void InsertSort(List<int> list) { for (int i = 0; i < list.Count; i++) { for (int j = i; j - 1 >= 0; j--) { if (list[j - 1] > list[j]) { int temp = list[j - 1]; list[j - 1] = list[j]; list[j] = temp; Console.WriteLine(string.Join(",", list)); } else break; } } } static void Main(string[] args) { List<int> list = new List<int>() { 9,8,10,7,6 }; InsertSort(list); Console.ReadKey(); }
我們可以看到,這種方法真是很笨。。就是一個一個往前插。。這當然不是我們想要的。。我們再改進下
public static void InsertSort2(List<int> list) { for (int i = 0; i < list.Count; i++) { int j = i; int baseNumber = list[j];//先把牌抽出來 for (; j - 1 >= 0; j--) { if (list[j - 1] > baseNumber) { list[j] = list[j - 1];//后面的往前推 } else break; } list[j] = baseNumber;//結束后把牌插入到空位 } } static void Main(string[] args) { List<int> list = new List<int>() { 9,8,10,7,6 }; InsertSort2(list); }
其實思路就是先抽出1張牌(比如抽出的那張牌的位置為3,注意:現在3是空出來的),如果前一張牌(位置2)比它大,就把2移到3上面去。2就空出來了。
接着再前面那張(位置1)如果比抽出來的牌大,繼續往前移。因為2空出來了,1移到2上。現在1空出來了。
然后把抽出來的牌放到1上,完成。
過程如下
8、9、10、7、6
7
8、9、10、 、6
8、9、 、10、6
8、 、9 、10、6
、8、9 、10、6
7、8、9 、10、6
再來看看執行效率方面到底差了多遠
public static void InsertSort(List<int> list) { for (int i = 0; i < list.Count; i++) { for (int j = i; j - 1 >= 0; j--) { if (list[j - 1] > list[j]) { int temp = list[j - 1]; list[j - 1] = list[j]; list[j] = temp; } else break; } } } public static void InsertSort2(List<int> list) { for (int i = 0; i < list.Count; i++) { int j = i; int baseNumber = list[j];//先把牌抽出來 for (; j - 1 >= 0; j--) { if (list[j - 1] > baseNumber) { list[j] = list[j - 1];//后面的往前推 } else break; } list[j] = baseNumber;//結束后把牌插入到空位 } } static void Main(string[] args) { List<int> list = new List<int>(); List<int> list2 = new List<int>(); Random random = new Random(); for (int i = 0; i < 50000; i++) { var temp = random.Next(); list.Add(temp); list2.Add(temp); } Stopwatch watch = new Stopwatch(); watch.Start(); InsertSort(list); watch.Stop(); Console.WriteLine(watch.ElapsedMilliseconds); watch.Reset(); watch.Start(); InsertSort2(list2); watch.Stop(); Console.WriteLine(watch.ElapsedMilliseconds); Console.ReadKey(); }
運行結果
快了將近1倍吧
第一種方法需要不短的交換2個元素。因為需要交換2個元素,所以我們還需要用1個臨時變量來保存其中1個元素的值
int temp = list[j - 1];
list[j - 1] = list[j];
list[j] = temp;
第二種方法則是直接將后面的元素往前移。
list[j] = list[j - 1];
如果說第一個種方法元素交換的次數為n,那第二種方法交換的次數則為 n/2+1。
堆排,快排時很多時候都會運用到這種思想。不知道大家有沒得到一些幫助呢?平時編程的時候是否也要注意到呢?