Python 排序算法小结

排序就是整理数据的序列,使其中元素按照特定的顺序排列的操作。排序可以使数据的存储方式更具有结构性。排序算法是算法的入门知识,每种算法都有其使用的场合,死记硬背很难记忆,理清算法的本质更有助于我们记忆。
这里写图片描述

对于每种排序方法,我们需要明白,每个算法的思想是什么?算法的稳定性如何,时间复杂度是多少,在什么情况下,算法出现最好(最坏)情况以及每种算法的具体实现。

插入排序:

顾名思义其基本操作是插入,不断把一个个元素插入到一个序列中,最终得到排序序列。为此只需维持好所构造序列的排序性质,最终就能自然地得到结果。

'''
当前需要排序的元素(data[i]),跟已经排序好的最后一个元素比较(data[i-1]),如果满足条件继续执行后面的程序,否则循环到下一个要排序的元素。
缓存当前要排序的元素的值,以便找到正确的位置进行插入。
排序的元素跟已经排序号的元素比较,比它大的向后移动(升序)。
要排序的元素,插入到正确的位置。
'''
    def insert_sort(data):
    for i in range(1,len(data)):   #i控制遍历所有的元素
        j = i                      #j控制当前元素
        while j>0 and data[j]<data[j-1]:
            data[j],data[j-1] = data[j-1],data[j]   #交换位置
            j -= 1

data = raw_input()    #通过IO读入数据,注意读入为string类型
data=[int(k) for k in data.split(' ')]#将读入的数据转换成int型list
insert_sort(data)
print data

考虑算法时间复杂度:外层循环总是要做n-1次,内层循环的次数与实际比较的情况有关。因此,若原数列已排序,则只进行外层的循环,此时为最好的情况,时间复杂度为O(n)。若原数列为逆序,则内层循环每一次都执行,此时为最坏的情况,时间复杂度为n*(n-1)/2,即为O(n**2)。考虑平均时间,假设插入个位置的概率相等,内层循环每次迭代j/2次,求和都得到n*(n-1)/2 复杂度仍为O(n**2),不难看出,算法是稳定的。

选择排序

思想(从小到大选择):
维护需要考虑的所有记录中的最小的i个记录的已排序序列。
每次从生育未排序的记录中选取值最小的记录,将其放在已排序序列记录的后面,自以为序列的第i+1个值,使已排序序列增长。
以空序列作为排序工作的开始,做到尚未排序的序列里只剩一个元素时,将其直接放在已排序序列的最后。
如果需要从大道小排序,只需要每次选择最大记录即可。

def SelectSort(data):
    if(len(data)<=1):
        return data
    for i in range(len(data)-1):    #外循环
        mins = i 
        for j in range(i,len(data)):  #寻找最小的元素
            if data[j]<data[mins]:
                mins = j
        if mins!=i :
            data[i],data[mins]=data[mins],data[i]
data = raw_input()    #通过IO读入数据,注意读入为string类型
data=[int(k) for k in data.split(' ')]#将读入的数据转换成int型list
SelectSort(data)
print data

直接选择排序的时间复杂度是O(n**2)。
直接选择排序不是稳定排序,举个小例子:
例如:(7) 2 5 9 3 4 [7] 1…当我们利用直接选择排序算法进行排序时候,(7)和1调换,(7)就跑到了[7]的后面了,原来的次序改变了,这样就不稳定了。

堆排序

堆排序是一种高效的选择排序算法,基于堆的概念,该算法高效的主要原因是在队里积累了前面所做的比较中得到的信息。由于堆的结构特征,这种信息可以很自然地重复利用。另一方面,由于堆和顺序表的关系,一个堆和一个排序序列可以很方便地嵌入同一个顺序表,不需要任何辅助结构。
采用堆排序技术,初始建堆需要O(n)的时间,随后每选择一个元素不超过O(log n)的时间,所以堆排序的时间复杂度是O(nlog n).堆排序是原地排序算法,所以空间复杂度为O(1).
堆排序不是稳定排序,在初始建堆和一系列选择元素后的筛选中,元素都沿着堆中的路径移动。这种移动路径与连续表里自然地顺序相互交叉,在这种移动中,很可能出现相同关键码记录的顺序被交换的情况。因此,对排序的特定决定了它的不稳定性,而且很难做出稳定的堆排序算法。
如果要得到从大到小的排序序列,每次选择的关键码应该是最小的记录(即选择小顶堆),反之选择大顶堆。
堆及其性质:
采用树形结构实现优先队列的一种有效技术成为堆。从结构上看,堆就是节点里存储数据的完全二叉树,但堆中数据的存储要满足一种特殊的堆序:任一个节点里所存的数据先于或等于其子节点里面的数据。
根据堆的定义,不难看到:
1. 在一个堆中从树根到任何一个叶结点的路径上,各结点里所存的数据按规定的优先关系(非严格)递减。
2. 堆中最优先的元素必定位于二叉树的根结点里(堆顶),O(1)的时间就能得到。
3. 位于树中不同路径上的元素,不关心其顺序关系。

基于上述理论,堆排序算法就分为下面几个步骤:
1. 把序列整理成大根堆
2. 把大根堆的根和序列最后一个数交换
3. 把除去最后一个数后剩下的子序列再变成大根堆
4. 重复2和3直到序列的第二个数为止

那么如何把一个序列变成大根堆,这是一个递归算法,大凡树结构相关的算法,递归总是少不了的,因为树本身就是一个递归结构。假设一棵树的左右子树已经是大根堆,那么要把这棵树变成大根堆的过程就是:
1. 在根和他的左右子节点中找到最大值:
2. 如果根就是最大值,什么都不做,这已经是大根堆
3. 如果根不是最大值,就把根和最大值交换位置。
4. 把交换后的那一棵树执行1到4步。
堆排序代码如下:

def heap_sort(data):
    def siftdown(data,begin,end):
        i,j=begin,begin*2+1
        term =data[i]
        while j <end:
            if j+1 <end and data[j+1]< data[j]:
                j+=1
            if data[i]>data[j]:
                break
            data[i] = data[j]
            i,j =j,j*2+1
        data[i] = term
    end = len(data)
    for i in range(end/2,-1,-1):
        siftdown(data,i,end)
    for i in range((end-1),0,-1):
        data[i],data[0] = data[0],data[i]
        siftdown(data,0,i)


data = [1,10,5,0,9]
heap_sort(data)
print data

冒泡排序

冒泡排序是一种最简单的排序方法,其通过交换元素消除逆序实现排序。其时间复杂度为O(n**2),是一种稳定的排序算法。
其代码如下:

def BubbleSort(data):
    for i in range(len(data)):
        for j in range(1,len(data)-i):
            if data[j-1]>data[j]:
                data[j-1],data[j]=data[j],data[j-1]
data = [5,9,2,8,4,1]
BubbleSort(data)
print data

冒泡算法的改进:
冒泡算法是基于交换的策略的排序算法,如果某一次交换中,发现没有遇到逆序,则说明排序工作已经完成,可以提前结束,基于此策略,改进算法的代码如下:

def SuperBubbleSort(data):
    for i in range(len(data)) :
        judge = 0             #设置一个警示位,处置
        for j in range(1,len(data)-i):
            if data[j-1]>data[j]:
                 data[j-1],data[j]=data[j],data[j-1]
                 judge = 1    #如果在本轮中发生了交换,则将其置为1
        if(judge ==0):        #如果在本轮中没交换,结束冒泡算法
            break
data = [5,9,2,8,4,1,100]
SuperBubbleSort(data)
print data

快速排序

快速排序是一种基于分治思想的算法,其思想是按某种标准把考虑的记录划分为“小记录”和“大记录”,并通过递归不断划分,最终得到一个排序的序列。其基本过程是:
1.选择一种标准,把被排序序列中的记录按这种标准分为大小两组。
2.采用同样的方式,递归地分别划分得到的这两组记录,并继续递归地划分下去
3.划分总是得到越来越小的数组,如此工作下去直到每个记录组中最多包含一个记录时,整个序列的排序完成。

# 切分过程,先在数组中选择一个数字,接下来把数组中的数字分成两部分,比选择小的数字移到
#数组的左边,比选择大的数字移到右边
import random 
#切分的第一种实现:固定基准円,标准实现
def partition(data,start,end):
   # index = random.randint(start,end)
    frist =start
    last = end
    index = data[start]
    #data[start],data[index]=data[index],data[start]
    while(frist<last):
        while(frist<last and data[last]>=index):
            last-=1
        data[frist]=data[last]
        while(frist<last and data[frist]<=index):
            frist+=1
        data[last]=data[frist]
    data[frist]=index
    print frist
    print data
    return frist
#切分的第一种实现,通过定义一个小数的区间,原地排序
def partition2(data,start,end):
    index = random.randint(start,end)
    #print data[index]
    small = start -1
    data[index],data[end]=data[end],data[index]
    for i in range(start,end):
        if(data[i]<data[end]):
            small+=1
            if(small!=i):
                data[small],data[i]=data[i],data[small]
    small+=1
    data[small],data[end]=data[end],data[small]
    return small    

def quicksort(data,start,end):
    if(start>=end):
        return 
    index = partition(data,start,end)
    print index
    if(index>start):
        quicksort(data,start,index-1)
    if(index<end):
        quicksort(data,index+1,end)     
if __name__ == '__main__':
    data=raw_input()
    data=map(int,data.split(' '))
    quicksort(data,0,len(data)-1)

其中:对于基准index的选择,可以使用随机选择,取第一个或最后一个元素,或者三数取中值的方法进行选择。随机选择和三数取中值策略能够避免因待排序列有序而造成的快速排序算法退化到冒泡的问题。
关于快排基准元选择的问题,如果读者更深入的去了解,请访问http://www.codeceo.com/article/3-sort-quick-sort-improve.html,这篇文章比较系统和详细的说明了这一问题。
快速排序时间复杂度为O(nlog(n)),最坏情况为O(n**2)。

归并排序

归并是一种典型的序列操作,其工作是把两个或更多有序序列合并成为一个有序序列。归并排序(二路)的基本方法如下:
初始时,把待排序序列中的n个记录看成n个有序子序列,每个子序列的长度为1.
把当时序列组里的有序子序列两两归并,完成一遍后序列组里的排序序列个数减半,每个子序列的长度加倍。
对加长的有序子序列重复上面的操作,最终得到一个长度为n的有序序列。
算法时间复杂度:最好的情况下:一趟归并需要n次,总共需要logN次,因此为O(N*logN)。最坏的情况下,接近于平均情况下,为O(N*logN)
说明:对长度为n的文件,需进行logN 趟二路归并,每趟归并的时间为O(n),故其时间复杂度无论是在最好情况下还是在最坏情况下均是O(nlgn)。
归并排序最大的特色就是它是一种稳定的排序算法。归并过程中是不会改变元素的相对位置的。缺点是,它需要O(n)的额外空间。但是很适合于多链表排序。

这里写图片描述
一种代码实现方式如下:

import sys
##sys.setrecursionlimit(1000000) #例如这里设置为一百万  
def merge(nums,first,middle,last):

    lnums = nums[first:middle+1]
    rnums = nums[middle+1:last+1]
    lnums.append(sys.maxint)   #保证两个数都能被归并到NUM中去
    rnums.append(sys.maxint)
    l = 0
    r = 0
    for i in range(first,last+1):
        if lnums[l]<rnums[r]:
            nums[i] = lnums[l]
            l+=1
        else:
            nums[i]=rnums[r]
            r+=1
def merge_sort(nums,frist,last):

    if frist<last:
        middle = (frist + last)/2
        merge_sort(nums,frist,middle)
        merge_sort(nums,middle+1,last)
        merge(nums,frist,middle,last)


if __name__=='__main__':
    nums =[10,2,5,7,8,4,6,8,9,3,1,0,-5,-3]
    print nums
    merge_sort(nums,0,13)
    print nums
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值