最简单的排序有三种:插入排序,选择排序和冒泡排序。它们的平均时间复杂度均为O(n^2),在这里对原理就不加赘述了。

贴出源代码:

插入排序:

  1. def insertion_sort(sort_list):
  2. iter_len = len(sort_list)
  3. if iter_len < 2:
  4. return sort_list
  5. for i in range(1, iter_len):
  6. key = sort_list[i]
  7. j = i - 1
  8. while j>=0 and sort_list[j]>key:
  9. sort_list[j+1] = sort_list[j]
  10. j =j - 1
  11. sort_list[j+1] = key
  12. return sort_list

冒泡排序:

  1. def bubble_sort(sort_list):
  2. iter_len = len(sort_list)
  3. if iter_len < 2:
  4. return sort_list
  5. for i in range(iter_len-1):
  6. for j in range(iter_len-i-1):
  7. if sort_list[j] > sort_list[j+1]:
  8. sort_list[j], sort_list[j+1] = sort_list[j+1], sort_list[j]
  9. return sort_list

选择排序:

  1. def selection_sort(sort_list):
  2. iter_len = len(sort_list)
  3. if iter_len < 2:
  4. return sort_list
  5. for i in range(iter_len-1):
  6. smallest = sort_list[i]
  7. location = i
  8. for j in range(i, iter_len):
  9. if sort_list[j] < smallest:
  10. smallest = sort_list[j]
  11. location = j
  12. if i != location:
  13. sort_list[i], sort_list[location] = sort_list[location], sort_list[i]
  14. return sort_list

其中:

sort_list[i], sort_list[location] = sort_list[location], sort_list[i]

是不是觉得很奇怪?没错,这是交换两个数的做法,通常在其他语言中如果要交换a与b的值,常常需要一个中间变量temp,首先把a赋给temp,然后把b赋给a,最后再把temp赋给b。但是在python中你就可以这么写:a, b = b, a,其实这是因为赋值符号的左右两边都是元组(这里需要强调的是,在python中,元组其实是由逗号“,”来界定的,而不是括号)。

平均时间复杂度为O(nlogn)的算法有:归并排序,堆排序和快速排序。

归并排序。对于一个子序列,分成两份,比较两份的第一个元素,小者弹出,然后重复这个过程。对于待排序列,以中间值分成左右两个序列,然后对于各子序列再递归调用。源代码如下,由于有工具函数,所以写成了callable的类:

  1. class merge_sort(object):
  2. def _merge(self, alist, p, q, r):
  3. left = alist[p:q+1]
  4. right = alist[q+1:r+1]
  5. for i in range(p, r+1):
  6. if len(left)>0 and len(right)>0:
  7. if left[0]<=right[0]:
  8. alist[i] = left.pop(0)
  9. else:
  10. alist[i] = right.pop(0)
  11. elif len(right)==0:
  12. alist[i] = left.pop(0)
  13. elif len(left)==0:
  14. alist[i] = right.pop(0)
  15.  
  16. def _merge_sort(self, alist, p, r):
  17. if p<r:
  18. q = int((p+r)/2)
  19. self._merge_sort(alist, p, q)
  20. self._merge_sort(alist, q+1, r)
  21. self._merge(alist, p, q, r)
  22.  
  23. def __call__(self, sort_list):
  24. self._merge_sort(sort_list, 0, len(sort_list)-1)
  25. return sort_list
堆排序,是建立在数据结构——堆上的。关于堆的基本概念、以及堆的存储方式这里不作介绍。这里用一个列表来存储堆(和用数组存储类似),对于处在i位置的元素,2*i+1位置上的是其左孩子,2*i+2是其右孩子,类似得可以得出该元素的父元素。
首先我们写一个函数,对于某个子树,从根节点开始,如果其值小于子节点的值,就交换其值。用此方法来递归其子树。接着,我们对于堆的所有非叶节点,自下而上调用先前所述的函数,得到一个树,对于每个节点(非叶节点),它都大于其子节点。(其实这是建立最大堆的过程)在完成之后,将列表的头元素和尾元素调换顺序,这样列表的最后一位就是最大的数,接着在对列表的0到n-1部分再调用以上建立最大堆的过程。最后得到堆排序完成的列表。以下是源代码:
  1. class heap_sort(object):
  2. def _left(self, i):
  3. return 2*i+1
  4. def _right(self, i):
  5. return 2*i+2
  6. def _parent(self, i):
  7. if i%2==1:
  8. return int(i/2)
  9. else:
  10. return i/2-1
  11.  
  12. def _max_heapify(self, alist, i, heap_size=None):
  13. length = len(alist)
  14.  
  15. if heap_size is None:
  16. heap_size = length
  17.  
  18. l = self._left(i)
  19. r = self._right(i)
  20.  
  21. if lalist[i]:
  22. largest = l
  23. else:
  24. largest = i
  25. if ralist[largest]:
  26. largest = r
  27.  
  28. if largest!=i:
  29. alist[i], alist[largest] = alist[largest], alist[i]
  30. self._max_heapify(alist, largest, heap_size)
  31.  
  32. def _build_max_heap(self, alist):
  33. roop_end = int(len(alist)/2)
  34. for i in range(0, roop_end)[::-1]:
  35. self._max_heapify(alist, i)
  36.  
  37. def __call__(self, sort_list):
  38. self._build_max_heap(sort_list)
  39. heap_size = len(sort_list)
  40. for i in range(1, len(sort_list))[::-1]:
  41. sort_list[0], sort_list[i] = sort_list[i], sort_list[0]
  42. heap_size -= 1
  43. self._max_heapify(sort_list, 0, heap_size)
  44.  
  45. return sort_list
最后一种要说明的交换排序算法(以上所有算法都为交换排序,原因是都需要通过两两比较交换顺序)自然就是经典的快速排序
先来讲解一下原理。首先要用到的是分区工具函数(partition),对于给定的列表(数组),我们首先选择基准元素(这里我选择最后一个元素),通过比较,最后使得该元素的位置,使得这个运行结束的新列表(就地运行)所有在基准元素左边的数都小于基准元素,而右边的数都大于它。然后我们对于待排的列表,用分区函数求得位置,将列表分为左右两个列表(理想情况下),然后对其递归调用分区函数,直到子序列的长度小于等于1。
下面是快速排序的源代码:
  1. class quick_sort(object):
  2. def _partition(self, alist, p, r):
  3. i = p-1
  4. x = alist[r]
  5. for j in range(p, r):
  6. if alist[j]<=x:
  7. i += 1
  8. alist[i], alist[j] = alist[j], alist[i]
  9. alist[i+1], alist[r] = alist[r], alist[i+1]
  10. return i+1
  11.  
  12. def _quicksort(self, alist, p, r):
  13. if p<r:
  14. q = self._partition(alist, p, r)
  15. self._quicksort(alist, p, q-1)
  16. self._quicksort(alist, q+1, r)
  17.  
  18. def __call__(self, sort_list):
  19. self._quicksort(sort_list, 0, len(sort_list)-1)
  20. return sort_list
细心的朋友在这里可能会发现一个问题,如果待排序列正好是顺序的时候,整个的递归将会达到最大递归深度(序列的长度)。而实际上在操作的时候,当列表长度大于1000(理论值)的时候,程序会中断,报超出最大递归深度的错误(maximum recursion depth exceeded)。在查过资料后我们知道,Python在默认情况下,最大递归深度为1000(理论值,其实真实情况下,只有995左右,各个系统这个值的大小也不同)。这个问题有两种解决方案:
1)重新设置最大递归深度,采用以下方法设置:
  1. import sys
  2. sys.setrecursionlimit(99999)
2)第二种方法就是采用另外一个版本的分区函数,称为随机化分区函数。由于之前我们的选择都是子序列的最后一个数,因此对于特殊情况的健壮性就差了许多。现在我们随机从子序列选择基准元素,这样可以减少对特殊情况的差错率。新的randomize partition函数如下:
  1. def _randomized_partition(self, alist, p, r):
  2. i = random.randint(p, r)
  3. alist[i], alist[r] = alist[r], alist[i]
  4. return self._partition(alist, p, r)

完整的randomize_quick_sort的代码如下(这里我直接继承之前的quick_sort类):

  1. import random
  2. class randomized_quick_sort(quick_sort):
  3. def _randomized_partition(self, alist, p, r):
  4. i = random.randint(p, r)
  5. alist[i], alist[r] = alist[r], alist[i]
  6. return self._partition(alist, p, r)
  7.  
  8. def _quicksort(self, alist, p, r):
  9. if p<r:
  10. q = self._randomized_partition(alist, p, r)
  11. self._quicksort(alist, p, q-1)
  12. self._quicksort(alist, q+1, r)

关于快速排序的讨论还没有结束。我们都知道,Python是一门很优雅的语言,而Python写出来的代码是相当简洁而可读性极强的。这里就介绍快排的另一种写法,只需要三行就能够搞定,但是又不失阅读性。(当然,要看懂是需要一定的Python基础的)代码如下:

  1. def quick_sort_2(sort_list):
  2. if len(sort_list)<=1:
  3. return sort_list
  4. return quick_sort_2([lt for lt in sort_list[1:] if lt<sort_list[0]]) + \
  5. sort_list[0:1] + \
  6. quick_sort_2([ge for ge in sort_list[1:] if ge>=sort_list[0]])
怎么样看懂了吧,这段代码出自《Python cookbook 第二版》,这种写法展示出了列表推导的强大表现力。
对于比较排序算法,我们知道,可以把所有可能出现的情况画成二叉树(决策树模型),对于n个长度的列表,其决策树的高度为h,叶子节点就是这个列表乱序的全部可能性为n!,而我们知道,这个二叉树的叶子节点不会超过2^h,所以有2^h>=n!,取对数,可以知道,h>=logn!,这个是近似于O(nlogn)。也就是说比较排序算法的最好性能就是O(nlgn)。
 
那有没有线性时间,也就是时间复杂度为O(n)的算法呢?答案是肯定的。不过由于排序在实际应用中算法其实是非常复杂的。这里只是讨论在一些特殊情形下的线性排序算法。特殊情形下的线性排序算法主要有计数排序,桶排序和基数排序。这里只简单说一下计数排序。
计数排序是建立在对待排序列这样的假设下:假设待排序列都是正整数。首先,声明一个新序列list2,序列的长度为待排序列中的最大数。遍历待排序列,对每个数,设其大小为i,list2[i]++,这相当于计数大小为i的数出现的次数。然后,申请一个list,长度等于待排序列的长度(这个是输出序列,由此可以看出计数排序不是就地排序算法),倒序遍历待排序列(倒排的原因是为了保持排序的稳定性,及大小相同的两个数在排完序后位置不会调换),假设当前数大小为i,list[list2[i]-1] = i,同时list2[i]自减1(这是因为这个大小的数已经输出一个,所以大小要自减)。于是,计数排序的源代码如下。
  1. class counting_sort(object):
  2. def _counting_sort(self, alist, k):
  3. alist3 = [0 for i in range(k)]
  4. alist2 = [0 for i in range(len(alist))]
  5. for j in alist:
  6. alist3[j] += 1
  7. for i in range(1, k):
  8. alist3[i] = alist3[i-1] + alist3[i]
  9. for l in alist[::-1]:
  10. alist2[alist3[l]-1] = l
  11. alist3[l] -= 1
  12. return alist2
  13.  
  14. def __call__(self, sort_list, k=None):
  15. if k is None:
  16. import heapq
  17. k = heapq.nlargest(1, sort_list)[0] + 1
  18. return self._counting_sort(sort_list, k)
各种排序算法介绍完(以上的代码都通过了我写的单元测试),我们再回到Python这个主题上来。其实Python从最早的版本开始,多次更换内置的排序算法。从开始使用C库提供的qsort例程(这个方法有相当多的问题),到后来自己开始实现自己的算法,包括2.3版本以前的抽样排序和折半插入排序的混合体,以及最新的适应性的排序算法,代码也由C语言的800行到1200行,以至于更多。从这些我们可以知道,在实际生产环境中,使用经典的排序算法是不切实际的,它们仅仅能做学习研究之用。而在实践中,更推荐的做法应该遵循以下两点:
当需要排序的时候,尽量设法使用内建Python列表的sort方法。
当需要搜索的时候,尽量设法使用内建的字典。
我写了测试函数,来比较内置的sort方法相比于以上方法的优越性。测试序列长度为5000,每个函数测试3次取平均值,可以得到以下的测试结果:
可以看出,Python内置函数是有很大的优势的。因此在实际应用时,我们应该尽量使用内置的sort方法。
由此,我们引出另外一个问题。怎么样判断一个序列中是否有重复元素,如果有返回True,没有返回False。有人会说,这不很简单么,直接写两个嵌套的迭代,遍历就是了。代码写下来应该是这样。
  1. def normal_find_same(alist):
  2. length = len(alist)
  3. for i in range(length):
  4. for j in range(i+1, length):
  5. if alist[i] == alist[j]:
  6. return True
  7. return False

这种方法的代价是非常大的(平均时间复杂度是O(n^2),当列表中没有重复元素的时候会达到最坏情况),由之前的经验,我们可以想到,利用内置sort方法极快的经验,我们可以这么做:首先将列表排序,然后遍历一遍,看是否有重复元素。包括完整的测试代码如下:

  1. import time
  2. import random
  3.  
  4. def record_time(func, alist):
  5. start = time.time()
  6. func(alist)
  7. end = time.time()
  8.  
  9. return end - start
  10.  
  11. def quick_find_same(alist):
  12. alist.sort()
  13. length = len(alist)
  14. for i in range(length-1):
  15. if alist[i] == alist[i+1]:
  16. return True
  17. return False
  18.  
  19. if __name__ == "__main__":
  20. methods = (normal_find_same, quick_find_same)
  21. alist = range(5000)
  22. random.shuffle(alist)
  23.  
  24. for m in methods:
  25. print 'The method %s spends %s' % (m.__name__, record_time(m, alist))

运行以后我的数据是,对于5000长度,没有重复元素的列表,普通方法需要花费大约1.205秒,而快速查找法花费只有0.003秒。这就是排序在实际应用中的一个例子。

文章来源:http://www.cnblogs.com/chineking/archive/2011/05/24/implement-sort-algorithm-with-python.html

用python实现各种排序算法的更多相关文章

  1. Python实现各种排序算法的代码示例总结

    Python实现各种排序算法的代码示例总结 作者:Donald Knuth 字体:[增加 减小] 类型:转载 时间:2015-12-11我要评论 这篇文章主要介绍了Python实现各种排序算法的代码示 ...

  2. Python实现常用排序算法

    Python实现常用排序算法 冒泡排序 思路: 它重复地走访过要排序的数列,一次比较两个元素,如果他们的顺序错误就把他们交换过来.走访数列的工作是重复地进行直到没有再需要交换,也就是说该数列已经排序完 ...

  3. python 的常见排序算法实现

    python 的常见排序算法实现 参考以下链接:https://www.cnblogs.com/shiluoliming/p/6740585.html 算法(Algorithm)是指解题方案的准确而完 ...

  4. 用 python 实现各种排序算法(转)

    常见几种排序的算法: 归并排序 归并排序也称合并排序,是分治法的典型应用.分治思想是将每个问题分解成个个小问题,将每个小问题解决,然后合并. 具体的归并排序就是,将一组无序数按n/2递归分解成只有一个 ...

  5. python基础===八大排序算法的 Python 实现

    本文用Python实现了插入排序.希尔排序.冒泡排序.快速排序.直接选择排序.堆排序.归并排序.基数排序. 1.插入排序 描述 插入排序的基本操作就是将一个数据插入到已经排好序的有序数据中,从而得到一 ...

  6. python实现简单排序算法

    算法 递归两个特点: 调用自身 有穷调用 计算规模越来越小,直至最后结束 用装饰器修饰一个递归函数时会出现问题,这个问题产生的原因是递归的函数也不停的使用装饰器.解决方法是,只让装饰器调用一次即可,那 ...

  7. Python实现八大排序算法(转载)+ 桶排序(原创)

    插入排序 核心思想 代码实现 希尔排序 核心思想 代码实现 冒泡排序 核心思想 代码实现 快速排序 核心思想 代码实现 直接选择排序 核心思想 代码实现 堆排序 核心思想 代码实现 归并排序 核心思想 ...

  8. python实现桶排序算法

    桶排序算法也是一种可以以线性期望时间运行的算法,该算法的原理是将数组分到有限数量的桶里,每个桶再分别排序. 它的算法流程如下所示: 设置一个定量的数组当作空桶子. 寻访序列,并且把项目一个一个放到对应 ...

  9. python实现八大排序算法

    插入排序 核心思想 插入排序的基本操作就是将一个数据插入到已经排好序的有序数据中,从而得到一个新的.个数加一的有序数据,算法适用于少量数据的排序,时间复杂度为 O(n^2).是稳定的排序方法.插入算法 ...

随机推荐

  1. Struts2 利用AJAX 导出大数据设置遮罩层

    Struts2 利用AJAX 导出大数据设置遮罩层 需求背景: 每次我们导出excel的时候 ,如果数据量很大,导出花费的时间会很长,页面却有没人任何反应,这个时候用户会认为系统有问题,要么关了页面, ...

  2. visibility和display

    visibility: hidden----将元素隐藏,但是在网页中该占的位置还是占着.display: none----将元素的显示设为无,即在网页中不占任何的位置.

  3. 2018 Jar_Feb_Newwords

    检测钩子程序 开发一个检测钩子程序的工具 - 豆丁网http://www.docin.com/p-1363993661.html pdf掺杂病毒的方法 Java的:xml文件中跳过的二进制数据在解析 ...

  4. 如何判断int类型相等

    int  a=10: int b=10: a==b  通过==判断两个int值是否相等. if(a==b){ 相等 }else{ 不相等 }

  5. Html解析类的新选择CsQuery

    今天在做一个html解析的方法,以前用HtmlAgilityPack或Winista.HTMLParser. 现在发现了一个巨好用的项目叫CsQuery,这货据说不仅能解析html还能提取css. 选 ...

  6. Ansible Playbook Conditionals

    通常,play的结果可能取决于变量的值,facts(有关远程系统的知识)或先前的任务结果. 在某些情况下,变量的值可能取决于其他变量. 此外,可以创建其他组,以根据主机是否与其他条件匹配来管理主机. ...

  7. HSTS详解

    1. 缘起:启用HTTPS也不够安全 有不少网站只通过HTTPS对外提供服务,但用户在访问某个网站的时候,在浏览器里却往往直接输入网站域名(例如www.example.com),而不是输入完整的URL ...

  8. httpclient4例子

    参考:http://hc.apache.org/httpclient-3.x/tutorial.html import org.apache.http.HttpEntity; import org.a ...

  9. 如何用INNO安装添加快捷启动方式到Win7的快速启动栏(超级任务栏)

    问题:如何用INNO安装添加快捷启动方式到Win7的快速启动栏(超级任务栏) 在XP下,添加方式是直接把快捷方式复制到%appdata%\Microsoft\Internet Explorer\Qui ...

  10. JDK-8不是有效的Win32应用程序