那些年学过的一些算法
今天在Google Rss看到一篇陈皓的文章:程序算法与人生选择 ,把职场的道理用程序员的的语言描述出来,教大家刚毕业时怎样择业,选择相对最好,最适合自己的工作. 文章中提到的一些算法让我想起了大学时候学习编程的日子,像排序算法是在「数据结构」课程学习的,贪婪算法是在「算法设计」课程中学习的,动态规划,dijstra 是在「运筹数学」中学习的。那时候主要都是用c,mathLab语言去写算法代码。这些经典的算法,是IT职业生涯最宝贵且永不过时的资源,因此看到陈皓的这篇文章时,想起了当年一个人在机房,或在宿舍自己的那台128M内存电脑上写代码的日子,所以想写点代码回忆下我们那些年学过的一些算法。
排序算法
排序算法是编程中每天都在用的知识,也是最基础的知识。排序算法有很多种,像:冒泡排序,选择排序,插入排序,快速排序,希尔排序,基数排序....... ,陈皓的文章中说到了其中两种是面试是必问(我招人时也喜欢问排序的一些知识),是冒泡排序和快速排序,这里我们详细讨论下这几种算法的思路和复杂度。
冒泡算法
冒泡排序思想比较简单,就是进行从底部开始,相邻比较两元素,把大的向上移,直到最大的元素移到了顶端时,我们进行第二轮,第三轮,第N轮相同运算,把第二大,第三大,第N大的元素相继向上移,最后一轮结束时,数据肯定是从大到小的顺序排列。每轮操作的过程就是把当前最大的元素移到最上面,像气泡向上冒似的,因此命名叫冒泡排序,也算比较形象。
1 def bubbleSort(): 2 originalArray = [2,9,5,1,7,20] 3 4 arrayLen = len(originalArray) 5 6 for i in range(arrayLen - 1): 7 for j in range(arrayLen -1 - i): 8 if originalArray[j] > originalArray[j+1]: originalArray[j],originalArray[j+1] = originalArray[j+1],originalArray[j] #两两比较后将较大值元素向上移 9 10 for a in originalArray: 11 print a,
复杂度:如果有N个数据项,第一遍比较冒泡需要比较N-1次,第二次需要比较N-2次,第N-1次需要比较1次,所以总共需要比较(1+2+3+...N-1)次,即(N2-N)/2 次,因为每次比较都有可能交换,也有可能不交换,所以交换数据的次数平均算下来是比较的一半 ,即(N2-N)/2 。用大O表示法即Ф(n2)
选择排序
看完冒泡排序,有一个很明显的浪费资源的地方,就是每一次轮回把最大的数据移到最顶端是通过不停的交换数据层层上移的,假设一下,如果一开始最底端的数据本来是这一轮中最大的数据项,但是冒泡算法还是要从下往上不停的比较,交换,你可能在想,通过一个什么办法我们把当前最大的数据项记下来,一轮比较结束后,直接把那个被标记的最大的数据项直接交换到最上端。
1 def selectSort(): 2 array = [2,9,5,1,7,20] 3 4 arrayLen = len(array) 5 6 for out in range(arrayLen - 1): 7 maxPosition = 0 #标记最大值的位置 8 for cur in range(arrayLen - out - 1): 9 if array[cur] < array[cur + 1]: 10 maxPosition = cur + 1 #如果比较时出现更大值时,更新标记 11 if maxPosition != arrayLen - out - 1: 12 array[arrayLen - out - 1],array[maxPosition] = array[maxPosition],array[arrayLen - out - 1] #交换一次位置 13 14 15 for a in originalArray: 16 print a,
选择排序相比冒泡排序而言,比较的次数是一样多(N2),但是在交换次数上做了优化,如果有N个数据项,最多只做N次交换。
插入排序
前面讲的「选择排序」虽然比「冒泡排序」在交换次数上做了优比,从N2降到了N,但是比较次数还是N2,有没有什么办法让比较次数减少一点呢?插入排序就是优化这里的。
插入排序的思路是:当我们从中间选定某一个数据项是,总是假设左边的所有数据已经是有序排列的,右边是无序的,待排列的数据项。正是这种局部有序性,可以使我们处理数据时不用比较所有的数据项。
1 def insertSort(): 2 array = [2,25,5,1,7,20] 3 4 arrayLen = len(array) 5 6 for out in range(arrayLen - 2,-1,-1): 7 insertPosition = out 8 for start in range(out + 1,arrayLen): 9 if array[out] < array[start]: 10 break 11 else: 12 insertPosition = start 13 if insertPosition != out: 14 array.insert(insertPosition+1,array[out]) #插入到正确位置,保证插入后仍然是局部有序的 15 del array[out] 16 17 for a in array: 18 print a,
「插入排序」相比「选择排序」,因为我们部是保证相对于上一次的处理结果是局部有序的,所以插入时我们进行比较大小只要进行到满足条件时,就不用继续比较下去,平均算下来,比较次数比「选择排序」少了一半。交换次数一样。
希尔排序
到现在为止,「插入排序」相对来说是性能最好的一种排序算法,特别是对于已经基本处于有序状态的序列,用插入排序是非常高效的,接近于Ф(N)。可是在极端情况(完全倒排情况)下,性能降到冒泡排序程序,因为每次的插入都要将前面局部有序的数据全部向后移动一次。
「希尔排序」就是建立在「插入排序」的基础之上,它对于前面说到的极端情况有特定的优化步骤:给定一组整数序列,如(40,13,4,1),分别以序列中的数据为跳跃步长进行插入排序,且最后一轮必定是步长为1。
上图中先以「4」为步长进行第一轮插入排序后,再以「1」为步长进行排序,比直接以「1」为步长排序少很多移位操作,原理很简单,因为「插入排序」的核心就是要序列满足「局部有序」,所以通过从大到小的步长排序后,
整体序列是越来越变得「局部有序」起来,这样最后以步长为「1」,即普通的插入排序进行时,算法复杂度变得越来越接近Φ(N)。
这个步长序列一般以公式:h = h * 3 + 1 生成。
1 def shellSort(): 2 array = [417,25,5,1,7,20,1,95,74,12,47,32,18,784,658,94,67,162] 3 4 step_seq = [13,4,1] #步长序列 5 6 for step in step_seq: # 以各个步长进行插入排序过程 7 for out in range(len(array) - 2,-1,-step): 8 insert_position = out 9 for start in range(out + 1,len(array)): 10 if array[out] < array[start]: 11 break 12 else: 13 insert_position = start 14 if insert_position != out: 15 array.insert(insert_position + 1,array[out]) 16 del array[out] 17 18 for a in array: 19 print a,
细心的你可能会发现这个算法其实就是「插入排序」一样的操作,只不过多了一个外围的「步长序列」循环操作。「希尔排序」从理论上算不出准确的算法复杂度,但是从各种数据测试大概得出为:Φ(N*(logN)2)。
快速排序
「快速排序」相对于前面的几种排序算法,可以算得上是一种较高级的排序算法,因为它有Φ(N*logN)的效率。「快速排序」其实和「归并排序」有一样的复杂度,但是「合并排序」需要两倍的内存空间。
「快速排序」的思路是:每次选定一个「基数」,把小于这个「基数」的所有数据移到左边,大于这个「基数」的移到右边,然后「递归」左边和右边的子序列,直到最后为单个点时,整个序列就是有序的。
1 array = [417,25,5,1,7,20,95,74,12,47,32,18,784,658,94,67,162] 2 def quickSort(): 3 partSort(0,len(array)-1) 4 for a in array: 5 print a, 6 7 def partSort(left,right): 8 if left >= right: 9 return 10 center = partition(left,right,array[right]) 11 partSort(left,center - 1) 12 partSort(center + 1,right) 13 14 def partition(left,right,base): 15 ori_right = right 16 left = left - 1 17 right = right 18 while True: 19 left = left + 1 20 while array[left] < base: 21 left = left + 1 22 23 right = right - 1 24 while 0 < right and array[right] > base: 25 right = right - 1 26 27 if left >= right: 28 break 29 else: 30 array[left],array[right] = array[right],array[left] #swap 31 array[left],array[ori_right] = array[ori_right],array[left] 32 return left 33 34 quickSort()