‘算法空間複雜度’,別覺得這個東西多麼高大上,我保證你看完這篇文章就能明白。算法
最近在啃算法,發現很是有趣。在我學習的過程當中發現了一個問題,那就是空間複雜度的問題,它絕對是效率的殺手。編程
關於空間複雜度的介紹(摘自百度)dom
空間複雜度(Space Complexity)是對一個算法在運行過程當中臨時佔用存儲空間大小的量度,記作S(n)=O(f(n))。好比直接插入排序的時間複雜度是O(n^2),空間複雜度是O(1) 。而通常的遞歸算法就要有O(n)的空間複雜度了,由於每次遞歸都要存儲返回信息。一個算法的優劣主要從算法的執行時間和所須要佔用的存儲空間兩個方面衡量。學習
拿插入排序來講。插入排序和咱們現實中打撲克牌時理清牌的那一步很像。拿鬥地主來講,咱們常常會把順子理出來,回想下咱們是怎麼理的?好比咱們有這麼5張牌九、八、十、七、6。過程應該是這樣的:spa
九、八、十、七、6pwa
從8開始,8發現9比它大,而後8插到9前面。code
8、9、十、七、6htm
而後到10,10發現它比前面2個都大,因此不用動。blog
八、九、10、七、6排序
而後到7,7發現10比它大,而後跟10換位置。
八、九、7、10、6
而後7又發現9也比它大,因而又跟9換位置
八、7、9、十、6
而後7又發現8也比它大,因而又跟8換位置
7、8、九、十、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上,完成。
過程以下
八、九、十、7、6
7
八、九、十、 、6
八、九、 、十、6
八、 、9 、十、6
、八、9 、十、6
七、八、9 、十、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。
堆排,快排時不少時候都會運用到這種思想。不知道你們有沒獲得一些幫助呢?平時編程的時候是否也要注意到呢?