‘算法空间复杂度’,别觉得这个东西多么高大上,我保证你看完这篇文章就能明白。算法
最近在啃算法,发现很是有趣。在我学习的过程当中发现了一个问题,那就是空间复杂度的问题,它绝对是效率的杀手。编程
关于空间复杂度的介绍(摘自百度)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。
堆排,快排时不少时候都会运用到这种思想。不知道你们有没获得一些帮助呢?平时编程的时候是否也要注意到呢?