python数据结构学习笔记-排序

1.排序算法的稳定性:

原有的序列中,两个元素同一经过排序后位置即使调换也符合排序要求,那么如果排序之后这两个元素保持原有的顺序,算法就是稳定的

3,1,1,2

排序后:

1,1,2,3

前两个1的顺序和原序列中的顺序一样,那么就是稳定的,也可以以元组排序为例,只对元组中的第一个元素排序,那么第二个元素不影响排序,如果两个元组第一个元素相同,第二个不同,那么这两个元组的前后顺序应该与排序前一致

2.冒泡排序:

思路:

每次比较当前元素和下一个元素的大小,如果当前的大,就和下一个交换位置,这样遍历完一遍后,最大的元素会在最末端,然后对剩下的元素再进行遍历,找出第二大的,以此类推

为了方便,这里用顺序表

def bubble_sort(alist):
    for j in range(len(alist)-1):
        for i in range(len(alist)-1-j):
            if alist[i]>alist[i+1]:
                alist[i], alist[i+1] = alist[i+1], alist[i]

如果某一次从头走到尾没有发生交换操作,说明此时序列已经是有序的了,因此可以改进

需要一个记录是否交换的count

def bubble_sort(alist):
    for j in range(len(alist)-1):
        count = 0
        for i in range(len(alist)-1-j):
            if alist[i]>alist[i+1]:
                alist[i], alist[i+1] = alist[i+1], alist[i]
                count += 1
        if count == 0:
            break  # 或者return

最优时间复杂度:O\left ( n\right ),本身就是排序好的

最坏时间复杂度:O\left ( n^{2}\right ),遇到的刚好是倒叙的序列

稳定性:稳定

 

3.选择排序:

每次把序列中最小的放到前面去

第一次找到最小的,让他和第一个位置的数据交换,第二次从第一个之后开始找,找到最小的把他和第二个未知的数据交换

def select_sort(alist):
    """选择排序"""
    # 写代码先写内部描述,也就是核心操作
    # 在写外部循环
    # 结合外部循环修改参数
    n = len(alist)
    for j in range(0, n-1):
        min_index = j
        for i in range(j+1, n):
            if alist[min_index] > alist[i]:
                min_index = i
        alist[j], alist[min_index] = alist[min_index], alist[j]

if __name__ == "__main__":
    li = [54,26,93,17,77,31,44,55,20]
    print(li)
    select_sort(li)
    print(li)

计算复杂度:

外循环:n-1次

内层循环:接近n次

所以时间复杂度:O\left ( n^{2}\right ),最优与最坏一样久

稳定性:不稳定(考虑升序每次选择最大而不是最小的情况)

 

4.插入排序:

插入排序和选择排序的区别:插入排序是对前面已经排好序的序列进行操作,不断往里面加入新的元素,选择排序是对后面未排序的序列进行操作

def insert_sort(alist):
    """插入排序"""

    for j in range(1, len(alist)): # 从列表(从零开始)的第一个元素到最后一个元素
        i = j  # i代表内存循环的起始值
        # 执行从右边的无序序列中的第一个元素,即i位置的元素,然后将其插入到前面的正确位置中
        while i > 0:
            if alist[i] < alist[i-1]:
                alist[i], alist[i-1] = alist[i-1], alist[i]
                i -= 1
            else:
                break  # 前面的都比他小,这里直接跳出,不用等循环结束

最优时间复杂度:O\left ( n\right ),本身就是排序好的

最坏时间复杂度:最坏O\left ( n^{2}\right )

稳定性:稳定

 

5.希尔排序: 

插入排序变种,有一个gap,每隔一个gap取一个元素,这些元素构成一个子序列

gap是几就有几个子序列,再对每个子序列进行插入排序,插入排序结束后,再调小gap,再进行插入排序

只到最后gap=1

如果gap一开始就是1,那么就相当于插入排序

# coding:utf-8
def shell_sort(alist):
    """希尔排序"""
    n = len(alist)
    gap = n // 2
    # gap = gap // 2
    while gap > 0:
        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 = gap // 2

最优时间复杂度:O\left ( n^{1.3}\right )

最坏时间复杂度:最坏O\left ( n^{2}\right )

平均时间复杂度:O\left ( n \log n \right )-O\left ( n^{2}\right )

稳定性:不稳定

 

6.快速排序: 

双指针,从两路进行排序,思路是找到目前排序元素应该在序列中的位置,一次排序后,该位置左边的数都比这个数小,右边的都比这个数大,这个数的位置不会再发生改变

实现原理(https://www.baidu.com/link?url=Aer6USlgrrndziU4aiDxNFDYMcLFHbB7YUSm70abCxVFUP3asGuRS1PjJhKnsOElxTcmv5ux7_hliVg3c-KnsTg9TyRBMfL70aY1_8zBQZq&wd=&eqid=847892e8002dea69000000035efddc00

2.1、设置两个变量 low、high,排序开始时:low=0,high=size-1。
2.2、整个数组找基准正确位置,所有元素比基准值小的摆放在基准前面,所有元素比基准值大的摆在基准的后面

默认数组的第一个数为基准数据,赋值给key,即key=array[low]。
因为默认数组的第一个数为基准,所以从后面开始向前搜索(high–),找到第一个小于key的array[high],就将 array[high] 赋给 array[low],即 array[low] = array[high]。(循环条件是 array[high] >= key;结束时 array[high] < key)
此时从前面开始向后搜索(low++),找到第一个大于key的array[low],就将 array[low] 赋给 array[high],即 array[high] = array[low]。(循环条件是 array[low] <= key;结束时 array[low] > key)
循环 2-3 步骤,直到 low=high,该位置就是基准位置。
把基准数据赋给当前位置。
2.3、第一趟找到的基准位置,作为下一趟的分界点。
2.4、递归调用(recursive)分界点前和分界点后的子数组排序,重复2.2、2.3、2.4的步骤。
2.5、最终就会得到排序好的数组。

# 54 26 93 17 77 31 44 55 20
# 根据定义,设置三个索引,low,high,以及当前排序的mid_value
# 先写核心操作代码:
# 一开始,mid_value是54,high指向20
# if alist[high] < mid_value:  # 20<54
#     alist[low] = alist[high]  # 20应该在mid_value左边,直接把他扔给low(此时的alist[low]比mid_value大)
#     low += 1
# elif alist[high] > mid_value:
#     high -= 1
# if alist[low] < mid_value:
#     low +=1
#
# elif alist[low] > mid_value:
#     alist[high] = alist[low]
#     high -= 1


def quick_sort(alist, first, last):
    if first >= last:  # 注意这里是大于等于,相当于只有first<last才执行下面的操作
        return
    mid_value = alist[first]  # key=array[low]
    low = first  #
    high = last  #
    while low < high:
        # high 左移
        while low < high and alist[high] >= mid_value:  # ‘=’的目的是让同样的值在一边
            high -= 1  # 如何防止索引超出,在每个while中加入终止条件high > low
        alist[low] = alist[high]  # 这里不用担心alist[low]丢失,因为mid_value = alist[low],这一次被移动的high放到了现在的low,对于第一次,也就是key的位置,该值不会再移动

        while low < high and alist[low] < mid_value:
            low += 1  # 如何防止索引超出,在每个while中加入终止条件high > low
        alist[high] = alist[low]  # 下一次low要被替换的值被保存到了high,该值不会再移动

    # 从循环退出时,low==high
    alist[low] = mid_value
    # 对low左边的进行快排
    quick_sort(alist, first, low - 1)  # 为什么不能用切片,因为用了切片,每次的子任务相当于是对新的列表排序(切片实质是建立了一个新的列表),没有对alist排序,但是如果传入的是下标就可以了
    # 对low右边的进行快排
    quick_sort(alist, low + 1, last)

alist = [54, 26, 93, 17, 77, 31, 44, 55, 20]
print('排序前:',alist)
n = len(alist)
quick_sort(alist, 0, n-1)
print('排序后:',alist)


时间复杂度:每一级的时间复杂度是n,一共要分\log _{2}n级,所以总的时间复杂度是O\left ( n\log _{2}n \right )

最坏时间复杂度:最坏O\left ( n^{2}\right )

稳定性:不稳定

 

6.归并排序: 

核心思路:保证子序列的是有序的,这样在合并子序列的时候,能保证遍正向遍历子序列的时候不会出现先遍历到大的值,再遍历到小的值

具体流程:

第一次拆分:对序列对半分

第二次拆分:再对拆分的序列分别对半分 ...

拆到每个子序列只有一个元素

然后相邻的子序列进行排序合并

对相邻的合并后的序列再进行排序时,用双指针

'''
第一次拆分:对序列对半分
第二次拆分:再对拆分的序列分别对半分
...
拆到每个子序列只有一个元素
然后相邻的子序列进行排序合并
对相邻的合并后的序列再进行排序时,用双指针
'''
def merge_sort(alist):
    n = len(alist)
    if n <= 1:
        return alist
    mid = n//2
    # 归并排序不是在列表本身操作,所以可以用切片
    # left是采用归并排序后形成的有序新的列表
    left = merge_sort(alist[:mid])
    right = merge_sort(alist[mid:])
    # 将两个有序的子序列合并成新的整体
    return merge(left, right)

def merge(left_li, right_li):
    left_point, right_point = 0, 0
    len_left = len(left_li)
    len_right = len(right_li)
    result = []
    while right_point<len_right and left_point<len_left:
        if left_li[left_point] <= right_li[right_point]:
            result.append(left_li[left_point])
            left_point += 1
        else:
            result.append(right_li[right_point])
            right_point += 1
    result += left_li[left_point:]
    result += right_li[right_point:]
    return result

alist = [54, 26, 93, 17, 77, 31, 44, 55, 20]
print('排序前:',alist)
n = len(alist)
a = merge_sort(alist)
print('排序后:',a)

所写代码的具体执行流程就是对原列表中相邻两个元素进行排序,然后将排序的结果与另外两个排序好的相邻的两个元素排序,这样就有四个排序好的元素,同样这四个排序好的元素再与另外四个(五个)排序好的元素排序...

时间复杂度:

切片拆分过程O\left ( 1 \right )

合并过程:每一次合并的复杂度是O\left ( n \right ),一共要合并log _{2}n

所以总的时间复杂度是O\left ( n\log _{2}n \right ),最坏最优都是O\left ( n\log _{2}n \right )

稳定性:稳定,拆分的时候稳定,合并的时候注意先判断左边是不是小于等于右边,也要注意是小于等于,这样同样的值,也是左边的先进入序列,这样就是稳定的了

空间复杂度:需要创建一个新的列表

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值