python数据结构与算法之排序

排序算法的稳定性:

假设有一串数据:(4,1)(3,1)(3,7)(5,6);要求按照第一个数排序,结果如下:

第一种:(3,1)(3,7)(4,1)(5,6)(3相同,维持原来的次序)

第二种:(3,7)(3,1)(4,1)(5,6)(3相同,次序被改变)

第一种是稳定的。

冒泡排序(以从小到大排为例):

每次走一遍把最大的元素冒泡,排到最后。

'''
冒泡排序:它也可以用于之前讲的链表什么的,只是交换部分稍微烦一点,思想一样。这里简单一点 ,以数字为例
'''
def bubble_sort(alist):
    '''冒泡排序,参数为列表'''
    n = len(alist)-1
    for j in range(n):
        for i in range(n-j):
            if alist[i]>alist[i+1]:
                # 前一个大于后一个,交换
                alist[i], alist[i+1] = alist[i+1], alist[i]    # 这样写在python这种动态语言中可以

if __name__ == '__main__':
    a = [2,0,5,1,10]
    bubble_sort(a)
    print(a)

冒泡排序的时间复杂度为:最坏可以认为是O(n^2),稳定的

改进:假如传入的序列就是有序的,比如[1,2,3,4,5,6]。此时按照上面代码还是要一步步比较,复杂度是一样的。改进之后,最优时间复杂度为O(n),最坏时间复杂度不变。

def bubble_sort(alist):
    '''冒泡排序,参数为列表'''
    n = len(alist)-1
    for j in range(n):
        count = 0
        for i in range(n-j):
            if alist[i]>alist[i+1]:
                # 前一个大于后一个,交换
                alist[i], alist[i+1] = alist[i+1], alist[i]    # 这样写在python这种动态语言中可以
                count += 1
        if count == 0:
            return 

选择排序:

思想解释:每次找到最小的值,与无序数中的一个数交换,比如:

a = [52,100,23,43,55,20,17,108]

找到最小值是17,将17与52交换,得:

a = [17,100,23,43,55,20,52,108]

看除了第一个数17外,其他最小的为20,与“第一个数”100交换:

a = [17,20,23,43,55,100,52,108]

此时,前面两个数已经有序,以此往下。

def select_sort(alist):
    """选择排序,既然是研究数据结构与算法,这里不用min()函数"""
    n = len(alist)
    for j in range(n-1):
        # 记录最小值的位置,这里首次默认是无序中的第一个
        min_index = j
        for i in range(j+1,n):
            if alist[i]<alist[min_index]:
                min_index = i
        alist[j], alist[min_index] = alist[min_index], alist[j]

选择排序的时间复杂度:O(n^2),不稳定

插入排序算法:

思想理解,与上面选择排序有点雷士,其实还是将序列无形的分为两部分。比如序列[52,100,23,43,55,20,17,108]。

将序列分为[52,                      100,23,43,55,20,17,108],第一部分是有序的。

然后将无序中的第一个100与有序中52比较,放在正确的位置[52,100,                      23,43,55,20,17,108],

同理接着比较23与[52,100],将其插入正确的位置[23,52,100,                            43,55,20,17,108]

 注意:插入的过程其实就是一个小排序,比如插入23时,先与100比,然后与52........

def insert_sort(alist):
    """插入排序"""
    for j in range(1,len(alist)):
        i = j
        # 从无序部分选一个插入到有序部分的过程
        while i>0:
            if alist[i]<alist[i-1]:
                alist[i], alist[i-1] = alist[i-1], alist[i]
                i -= 1
            else:
                # 因为有序部分是有序的,只要前一个数比当前数小,那前面所有的数都比当前数小
                break

最坏时间复杂度是O(n^2),最优时间复杂度是O(n),稳定的

希尔排序:

它其实就是插入排序的改进版,思想百度百科一下就可以了。

简单介绍:它有一个gap(间隙),假设gap=4。原序列为54,26,93,17,77,31,44,55,20;

因为gap=4,索引相差四的抽出,那原序列变成4层:

54,                    77 ,             20

     26,                    31,

          93,                    44

              17                        55

每一层排序后再插入回去,即经过第一次排序之后:20,26,44,17,54,31,93,55,77;

再取gap=2,原序列变成两层:

20          44           54            93          77

      26          17          31              55

同理每层子序列用插入算法,再取gap=1.得到最后的结果。

def shell_sort(alist):
    """希尔排序,核心部分其实是插入排序"""
    n = len(alist)
    gap = n//2        # 其实有最优的gap值,这里直接用长度的一半取整

    # gap变化为0前,插入算法执行的次数
    while gap>=1:
        # 内层循环其实就是插入算法,与普通的插入算法的区别在于gap
        for j in range(gap,n):
            i = j
            while i>0:
                if alist[i]<alist[i-gap]:
                    alist[i], alist[i-gap] = alist[i-gap], alist[i]
                    i -= gap
                else:
                    break
        gap //= 2

最坏时间复杂度O(n^2),最优时间复杂度根据gap值不同的而不同(优化问题)。不稳定

快速排序

具体思想介绍可以百度一下,其实有点像冒泡的改进。举个例子:

假设原序列为:54,26,93,17,77,31,44,55,20;

先找到第一个数54在有序情况下的位置,怎么找?设定两个游标,游标low先指向26,游标high先指向最后一个数20.

当low指向的数小于54时,可以继续向high的方向移动,否则先静止;同理当high指向的数大于54时可以向low的方向移动,否则静止;

按照上面的要求,此时54,26,93(low),17,77,31,44,55,20(high);

卡主不动了,这时候交换93,20的位置,数据变成54,26,20(low),17,77,31,44,55,93(high);这时不卡了,继续移动(先动low),一直到low与high重合,如下:

54,          26,20,17,31,44(low_and_high),77,55,93

此时确定54的位置:26,20,17,31,44,                  54                     ,77,55,93同理处理54前后两部分;

 

大致思想是这样,为了方便编程,稍微变通一下(仍然是快速排序):

序列还是上面那个,一开始low指向54,high指向20,设一个mid_value=54(把第一个数存起来),游标还是往中间动;

high先动,能动的条件一样,此时20,high游标不能动,就将20代替low指向的数(54早已存起来)。变成20(low),26,93,17,77,31,44,55,空(high);

这时候low动起来,一直到:20,26,93(low),17,77,31,44,55,空(high)

替换:20,26,空(low),17,77,31,44,55,93(high);high动

最终:20,26,44,17,31,空(low_high),77,55,93

让空=54,20,26,44,17,31,           54,            77,55,93,同理处理54两边。

 1 def quick_sort(alist):
 2     """快速排序,错误示范,错误的地方在下面递归"""
 3     n = len(alist)
 4     mid_value = alist[0]
 5     low = 0
 6     high = n-1
 7 
 8     while low < high:
 9         # high左移动(等于的情况放一边处理比较好)
10         while low < high and alist[high] >= mid_value:
11             high -= 1
12         alist[low] = alist[high]
13         # low右移
14         while low < high and alist[low] < mid_value:
15             low += 1
16         alist[high] = alist[low]
17     # 从while退出,low等于high
18     alist[low] = mid_value
19 
20     # mid_walue两边处理方式与上面一样
21     quick_sort(alist[:low-1])        #不能切片传,这等于传一个新的列表了,也就是说操作的不是一个列表
22     quick_sort(alist[low+1:])
错误示范
def quick_sort(alist, first, last):
    """
    快速排序
    :param alist: 列表
    :param first: 列表的第一个元素索引,0
    :param last: 列表最后一个元素,len()-1
    :return:
    """
    # 递归的终止条件
    if first >= last:
        return

    mid_value = alist[first]
    low = first
    high = last

    while low < high:
        # high左移动(等于的情况放一边处理比较好)
        while low < high and alist[high] >= mid_value:
            high -= 1
        alist[low] = alist[high]
        # low右移
        while low < high and alist[low] < mid_value:
            low += 1
        alist[high] = alist[low]
    # 从while退出,low等于high
    alist[low] = mid_value

    # mid_walue两边处理方式与上面一样
    quick_sort(alist, first, low-1)
    quick_sort(alist, low+1, last)


if __name__ == '__main__':
    b = [54,26,93,17,77,31,44,55,20]
    quick_sort(b,0,len(b)-1)
    print(b)

快速排序的最优时间复杂度O(nlogn),最坏时间复杂度O(n^2),不稳定。

归并排序:

思想:百度一下就好

def Merge_Sort(lists):
    if len(lists) <= 1:
        return lists
    mid = int(len(lists) / 2)
    # left 采用归并排序后形成的有序的新的列表
    left = Merge_Sort(lists[:mid])
    # right 采用归并排序后形成的有序的新的列表
    right = Merge_Sort(lists[mid:])
    # 将两个有序的子序列合并
    # Merge(left, right)
    r, l=0, 0  # 为两个指针
    result=[]
    while l<len(left) and r<len(right):
        if left[l] < right[r]:
            result.append(left[l])
            l += 1
        else:
            result.append(right[r])
            r += 1
    result += list(left[l:])
    result += list(right[r:])
    return result



if __name__ == '__main__':
    b = [54,26,93,17,77,31,44,55,20]
    b = Merge_Sort(b)
    print(b)

上面代码递归有点复杂,执行流程可看https://www.bilibili.com/video/av21540971/?p=35

最坏与最优时间复杂度都是:O(nlogn),稳定。

堆排序没介绍,可以百度一下,快速排序用的比较多。

 

posted @ 2019-03-13 10:44  maxiaonong  阅读(241)  评论(0编辑  收藏  举报