数据结构与算法python—14.排序之九种排序详解

引言

  排序这部分,会介绍9种基础的排序及其优化

  1. 计数排序
  2. 基数排序
  3. 冒泡排序
  4. 桶排序
  5. 选择排序
  6. 快速排序
  7. 插入排序
  8. 归并排序
  9. 希尔排序

一、冒泡排序

  冒泡排序是一种简单的排序算法。它重复地走访过要排序的数列,一次比较两个元素,如果它们的顺序错误就把它们交换过来。走访数列的工作是重复地进行直到没有再需要交换,也就是说该数列已经排序完成。这个算法的名字由来是因为越小的元素会经由交换慢慢“浮”到数列的顶端。冒泡排序总结来说,就是相邻对比交换位置,直至无需再交换。其动画演示为:

在这里插入图片描述

# 冒泡排序
def bubble_sort(alist):
    '''冒泡排序'''
    n = len(alist)
    count = 0
    for j in range(0, n - 1):
        swap = False
        print(alist)
        # 经过j,确定了j个元素,后面的不需要再比较
        for i in range(0, n - 1 - j):
            count += 1
            # i指的是下标,从头走到尾,走到n-2,与n-1比较即可
            if alist[i] > alist[i + 1]:
                swap = True
                alist[i], alist[i + 1] = alist[i + 1], alist[i]
        # swap = False时,表明无需再交换,直接返回
        if not swap:
            break

    print(f"总循环次数{count}")
    return alist


if __name__ == '__main__':
    li = [3, 4, 2, 1, 5, 6, 7, 8]
    print('bubble_sort end:', bubble_sort(li))
[3, 4, 2, 1, 5, 6, 7, 8]
[3, 2, 1, 4, 5, 6, 7, 8]
[2, 1, 3, 4, 5, 6, 7, 8]
[1, 2, 3, 4, 5, 6, 7, 8]
总循环次数22
bubble_sort end: [1, 2, 3, 4, 5, 6, 7, 8]

代码优化:

优化思路︰在排序的过程中,数据可以从中间分为两段,一段是无序状态,另一段是有序状态。每一次循环的过程中,记录最后一个交换元素,它便是有序和无序状态的边界
下一次仅循环到边界即可,从而减少循环次数,达到优化。当然如果没有有序部分,则不会减少循环次数。

# 冒泡排序
def bubble_sort(alist):
    '''冒泡排序'''
    n = len(alist)
    count = 0
    # 记录最后一个交换元素坐标
    last_change_index = 0
    # 有序与无序的分界线
    border = n - 1
    for j in range(0, n - 1):
        swap = False
        print(alist)
        # 经过j,确定了j个元素,后面的不需要再比较
        for i in range(0, border):
            count += 1
            # i指的是下标,从头走到尾,走到n-2,与n-1比较即可
            if alist[i] > alist[i + 1]:
                swap = True
                alist[i], alist[i + 1] = alist[i + 1], alist[i]
                last_change_index = i

        # swap = False时,表明无需再交换,直接返回
        if not swap:
            break
        border = last_change_index

    print(f"总循环次数{count}")
    return alist


if __name__ == '__main__':
    li = [3, 4, 2, 1, 5, 6, 7, 8]
    print('bubble_sort end:', bubble_sort(li))
[3, 4, 2, 1, 5, 6, 7, 8]
[3, 2, 1, 4, 5, 6, 7, 8]
[2, 1, 3, 4, 5, 6, 7, 8]
[1, 2, 3, 4, 5, 6, 7, 8]
总循环次数10
bubble_sort end: [1, 2, 3, 4, 5, 6, 7, 8]

时间复杂度:

  • 最优时间复杂度:O(n) (表示遍历一次发现没有任何可以交换的元素,排序结束。)
  • 最坏时间复杂度:O(n^2)(数据一开始就是倒序的,需要经过n-1次冒泡)

空间复杂度:

  • 空间复杂度是O(1),这类算法也可以叫做原地排序算法。

排序算法的稳定性:

  • 稳定性怎么理解呢?
    如果待排序的序列中存在值相等的元素,经过排序之后,相等元素之间原有的先后顺序不变。

总结:
冒泡排序虽然使用了数组存储数据但是并没有使用数组随机访问的特性,因此改用链表这种存健结构,使用冒泡排序仍然是可以实现的。

二、选择排序

  选择排序(Selection sort)是一种简单直观的排序算法。其工作原理为:首先在未排序序列中找到最小(大)元素,存放到排序序列的起始位置,然后,再从剩余未排序元素中继续寻找最小(大)元素,然后放到已排序序列的末尾。以此类推,直到所有元素均排序完毕。通俗点理解就是拿出最大或最小,然后在剩余中拿出最大或最小。其动画演示为:
在这里插入图片描述
  选择排序的主要优点与数据移动有关。如果某个元素位于正确的最终位置上,则它不会被移动。选择排序每次交换一对元素,它们当中至少有一个将被移到其最终位置上,因此对n个元素的表进行排序总共进行至多n-1次交换。在所有的完全依靠交换去移动元素的排序方法中,选择排序属于非常好的一种。

# 选择排序策略是拿出最小,然后在剩余中拿出最小
def selection_sort(aList):
    n = len(aList)
    count = 0

    # 需要n-1次遍历寻找
    for i in range(0, n - 1):
        print(aList)
        # 存储最小值下标
        min_index = i
        for j in range(i + 1, n):
            count += 1
            if aList[min_index] > aList[j]:
                min_index = j

        # 判断min_index与i的关系
        if min_index != i:
            # 元素互换
            aList[i], aList[min_index] = aList[min_index], aList[i]

    print(f"总循环次数为{count}")
    return aList


if __name__ == '__main__':
    li = [3, 4, 2, 1, 8, 6, 5, 7]
    print('select_sort end:', selection_sort(li))
[3, 4, 2, 1, 8, 6, 5, 7]
[1, 4, 2, 3, 8, 6, 5, 7]
[1, 2, 4, 3, 8, 6, 5, 7]
[1, 2, 3, 4, 8, 6, 5, 7]
[1, 2, 3, 4, 8, 6, 5, 7]
[1, 2, 3, 4, 5, 6, 8, 7]
[1, 2, 3, 4, 5, 6, 8, 7]
总循环次数为28
select_sort end: [1, 2, 3, 4, 5, 6, 7, 8]

代码优化版本:
在开头:拿大,然后剩余部分取大;尾部:拿小,然后剩余部分取小,可以对代码进行优化,减少循环次数。优化点是开头末尾同时选择排序,当最小值的索引 + 1 = 尾部有序部分第一个元素索引时,表明已经排好序了。

# 选择排序策略是拿出最小,然后在剩余中拿出最小
def selection_sort(aList):
    n = len(aList)
    count = 0

    # 需要n-1次遍历寻找
    for i in range(0, n - 1):
        print(aList)
        # 存储最小值下标
        min_index = i
        # 存储最大值下标
        max_index = n - i - 1
        for j in range(i + 1, n - i - 1):
            count += 1
            # 更新最小值索引
            if aList[min_index] > aList[j]:
                min_index = j
            # 更新最大值索引
            if aList[max_index] < aList[j]:
                max_index = j

        if max_index == min_index + 1:
            break

        # 判断min_index与i的关系
        if min_index != i:
            # 元素互换
            aList[i], aList[min_index] = aList[min_index], aList[i]

        # 判断max_index与i的关系
        if max_index != n - i - 1:
            # 元素互换
            aList[max_index], aList[n - i - 1] = aList[n - i - 1], aList[max_index]

    print(f"总循环次数为{count}")
    return aList


if __name__ == '__main__':
    li = [3, 4, 2, 1, 8, 6, 5, 7]
    print('select_sort end:', selection_sort(li))
[3, 4, 2, 1, 8, 6, 5, 7]
总循环次数为6
select_sort end: [3, 4, 2, 1, 8, 6, 5, 7]

时间复杂度:

  • 选择排序无论数据初始是何种状态,均需要在未排序元素中选择最小或最大元素与未排序序列中的首尾元素交换,因此它的最好、最坏、平均时间复杂度均为 O ( n 2 ) O(n^2) O(n2)

稳定性:

  • 选择排序在未排序区间选择一个最小值,与前面的元素交换,对于值相同的元素,因为交换会破坏他们的相对顺序,因此它是一种不稳定的排序算法。

空间复杂度:

  • 只需要选择一个变量作为交换,因此,空间复杂度为O(1),是一种原地排序算法。

三、插入排序

  插入排序(英语:Insertion Sort)是一种简单直观的排序算法。其工作原理为:通过构建有序序列,对于未排序数据,在已排序序列中从后向前扫描,找到相应位置并插入。插入排序在实现上,在从后向前扫描过程中,需要反复把已排序元素逐步向后挪位,为最新元素提供插入空间。通俗来讲,就是将未排序的,在已排序中找到位置插入进去。其动画演示为:
在这里插入图片描述

def insert_sort(aList):
    count = 0
    n = len(aList)
    # 从第1个位置处开始遍历
    for i in range(1, n):
        print(aList)
        # 中间变量
        tmp = aList[i]
        j = i
        # j从后往前遍历,寻找合适插入位置
        while j > 0:
            count += 1
            if tmp < aList[j - 1]:
                aList[j] = aList[j - 1]
            else:
                break
            j -= 1
        # 循环结束时,j处是合适的插入位置
        aList[j] = tmp
    print(f'总循环次数{count}')
    return aList

if __name__ == '__main__':
    li = [1, 3, 4, 2, 1, 5, 6, 7, 8, 4]
    print(&
  • 2
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 6
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值