10大排序算法(python实现)

本文详细介绍了10种常见的排序算法,包括冒泡排序、直接插入排序、简单选择排序、快速排序、希尔排序、堆排序、归并排序、计数排序、桶排序和基数排序。对每种算法的动图演示、代码示例和时间复杂度进行了分析,特别强调了稳定性和内外排序的区别。
摘要由CSDN通过智能技术生成

        

目录

 思维导图

一.3种基本排序算法

01冒泡排序

动图演示

代码示例

02直接插入排序

动图演示

代码示例

03简单选择排序

动图演示

 代码示例

二.基本排序算法的改进

04快速排序

动图演示

​ 代码示例

05希尔排序

图片演示

 代码示例

06堆排序

动图演示

代码示例 

三.归并类 

07归并排序

图片演示

 代码示例 

 四.不比较元素大小的排序

08计数排序

图片演示

代码示例

09桶排序

图片演示

代码示例

10基数排序

图片演示

代码示例

五.总结


排序和查找作为典型计算机科学问题,也是算法学习中的基础.今天,我们一起来看看10种常见的排序算法的实现.

排序:排序是指将集合中的元素按照某种顺序(升序/降序)排列的过程

排序算法分类:内排序和外排序

这里有一个误区,这里的内外排序,要和原地排序/占用额外空间相区分

内外:是指排序仅使用内存还是要使用外存的数据(如硬盘),外排序主要用于带排序数据量很大,无法仅使用内存就完成排序,研究的问题时排序过程中内存和外存数据交换的问题,本文的排序算法都属于内排序

原地排序/占用额外空间:排序过程中是否使用除给定序列占用空间以外的内存空间

 思维导图

 

一.3种基本排序算法

01冒泡排序

动图演示

代码示例

'''冒泡排序
思想:扫描n-1轮
第一趟两两比较相邻记录,反序则交换,可以将最大的元素,交换到最后的位置
第二趟....可以将第二大的元素,交换到最后的位置
...
算法分析:
1\时间复杂度:
    最好:O(n)
    最坏:O(n**2)
    平均:O(n**2)
2\空间复杂度:
    O(1)
3\ 稳定性:
    稳定排序'''
# 优化思路:当一趟没有交换元素时,表示排序完成,打断循环

def bubbleSort(lis):
    for i in range(len(lis)-1): # 趟数
        state = True # 用于判断有没有发生交换
        for j in range(len(lis)-1-i): # 每趟交换的次数
            if lis[j] > lis[j+1]:
                lis[j],lis[j+1] = lis[j+1],lis[j]
                state = False
        if state:
            break

lis = [1,6,3,9,2,6,]
bubbleSort(lis)
print(lis)


02直接插入排序

动图演示

代码示例

'''直接插入排序
思想:扫描n-1轮
假设第一个元素已排好队,从第二个元素开始扫描,按正确的位置将元素依次插入到有序序列
算法分析:
1\时间复杂度:
    最好:O(n)
    最坏:O(n**2)
    平均:O(n**2)
2\空间复杂度:
    O(1)
3\ 稳定性:
    稳定排序'''

def insertion_sort(alist):
    for i in range(1,len(alist)):  # 扫描的轮数 从第二个元素开始扫描
        j = i -1
        tmp = alist[i]  # 待插入的记录
        while j >= 0 and tmp < alist[j]:  # 需要移动的次数
            alist[j+1] = alist[j]
            j -= 1
        alist[j+1] = tmp
        print('第{}轮排序的结果为:{}'.format(i, alist))

lis = [2,5,3,7,9]
insertion_sort(lis)
print(lis)

03简单选择排序

动图演示

 代码示例

'''选择排序
思想:扫描n-1轮
每一趟选出最小的元素,放在已排好序的序列的后面.
算法分析:
1\时间复杂度:
    最好:O(n**2)
    最坏:O(n**2)
    平均:O(n**2)
2\空间复杂度:
    O(1)
3\ 稳定性:
    不稳定排序'''
def selection_sort(alist):
    for i in range(len(alist)-1):  # 扫描轮数
        min_index = i
        for j in range(i+1,len(alist)):
            if alist[j] < alist[min_index]:
                alist[j],alist[min_index] = alist[min_index],alist[j]
        print('第{}轮排序的结果为:{}'.format(i+1,alist))

lis = [2,5,9,3,7]
selection_sort(lis)
print(lis)

二.基本排序算法的改进

04快速排序

动图演示

 代码示例

'''快速排序
思想:
1.快速排序属于交换类排序
2.该算法运用分治的思想
    1>分而治之  把大问题化为子问题
    2>子问题和大问题的解决方案类似
    3>存在最小子问题
    4>合并子问题的答案得到大问题的答案
3.步骤
    1>找到带排序序列的基准值
        一般选取第一个元素
        也可通过三数取中法确定(首中尾)
    2>将原序列分割为比基准值小的子区间和比基准值大的子区间
    3>分别对子区间递归调用快速排序
    4>递归边界:
        子区间只有一个元素或子区间为空
    5>固定基准值的方法:
        1)选取第一个元素为 pivot = alist[start]
        2)左右指针分别指向第二个元素和最后一个元素
            left = start + 1  /
            right = last
        3)只要左右指针不交叉
            ~right持续-1,遇到比基准值小的元素,停止
            ~left持续+1,遇到比基准值大的元素,停止
            ~交换left和right
        4)交换基准值和right位置的元素

算法分析:
1\时间复杂度:
    平均:O(nlogn)
    最好:O(nlogn)
    最坏:O(n**2)
2\空间复杂度:
    最好: O(logn) 递归栈占用的空间
    最坏: O(n)   tmp占用的空间
3\ 稳定性:
    不稳定排序'''
import random


def quickSort(alist, start, end):
    if start < end:
        pos = partition(alist, start, end)  # 返回基准值位置
        quickSort(alist, start, pos - 1)  # 递归
        quickSort(alist, pos + 1, end)


def partition(alist, start, end):
    index = start  # 基准值索引
    pivot = alist[start]  # 基准值
    left = start + 1
    right = end
    find = False
    while not find:
        while left <= right and alist[right] >= pivot:  # 先移动右指针
            right -= 1
        while left <= right and alist[left] <= pivot:
            left += 1
        if left < right:
            alist[left], alist[right] = alist[right], alist[left]  # 交换左右元素
        else:
            find = True
    alist[index], alist[right] = alist[right], alist[index]  # 交换基准值和right
    return right


if __name__ == '__main__':
    lis = list(range(10))
    random.shuffle(lis)
    print(lis)
    quickSort(lis, 0, len(lis) - 1)
    print(lis)

 

05希尔排序

图片演示

 代码示例

'''希尔排序
思想:
1.希尔排序是对直接插入排序的改进,改进的思想是直接插入排序在数据元素较少\元素基本有序的情况下复杂度较低
2.希尔排序通过对元素分组,然后对每一组元素应用插入排序.分组的依据是按增量分组
3.增量选取的原则:
    1>最后一个增量必为1
    2>避免增量互为倍数
4.希尔排序效率依赖增量的选取
    len // 2 (一般选取方法)
算法分析:
1\时间复杂度:
    最好:O(n)
    最坏:O(n**2)
    平均:O(n**1.5)
2\空间复杂度:
    O(1)
3\ 稳定性:
    不稳定排序'''


def shell_sort(alist):
    lengh = len(alist)
    gap = lengh // 2   # 选取增量
    while gap >= 1:
        for i in range(gap,lengh):
            '''插入排序'''
            j = i - gap
            tmp = alist[i]
            while j >= 0 and tmp < alist[j]:
                alist[j+ gap] = alist[j]
                j -= gap
            alist[j+gap] = tmp

        gap //= 2

lis = [2,5,3,7,9,-4,4,10,20,53,130]
shell_sort(lis)
print(lis)

06堆排序

动图演示

代码示例 

'''堆排序
思想:
1.堆排序属于选择类排序 是直接选择排序的升级版
2.元素之间只比较一次
3.步骤
    1>建堆(升序建最大堆)
        根据待排序的序列使用build函数建堆
    2>堆顶元素(首)和尾交换,则当前堆中最大的元素放在了正确的位置上
    3>调整堆,做logn次下沉
    4>重复2.3两步,直到只有1个元素,将它放到正确的位置,排序结束
算法分析:
1\时间复杂度:
    平均:O(nlogn)
    最好:O(nlogn)
    最坏:O(nlogn)
2\空间复杂度:
    O(1)
3\ 稳定性:
    不稳定排序'''

def shiftDown(heapList,index,lengh):
    '''下沉元素,比较根节点和左右儿子节点,并和较大的交换
    步骤:
    1.如果左儿子存在  即左儿子的索引在列表的索引范围内,则循环执行一下操作
    2.如果左儿子大于根,同时大于右儿子,则交换根和左儿子
    3.如果右儿子存在,并大于左儿子,则交换根和右儿子
    '''
    while 2 * index + 1 < lengh:
        left = 2 * index + 1
        right = 2 * index + 2
        if (right > lengh-1 or (right <= lengh-1 and heapList[left] > heapList[right])) \
                and heapList[left] > heapList[index]:
            heapList[left], heapList[index] = heapList[index], heapList[left]
            index = left
        elif right <= lengh-1 and heapList[right] > heapList[left] and heapList[right] > \
                heapList[index]:
            heapList[right], heapList[index] = heapList[index], heapList[right]
            index = right
        else:
            break


def buildHeap(Lis):
    '''根据已有列表新建一个堆
    直接在列表上进行下沉操作,以满足堆的特点'''
    mid = len(lis) // 2 - 1
    while mid >= 0:
        shiftDown(lis,mid,len(lis))
        mid -= 1

def heap_sort(alist):
    buildHeap(alist)
    heap_sort_helper(alist,len(alist))


def heap_sort_helper(alist,lengh):
    while lengh > 1:
        alist[0],alist[lengh-1] = alist[lengh-1],alist[0]
        lengh -= 1
        shiftDown(alist,0,lengh)






if __name__ == '__main__':
    lis = [2,5,7,8,9,3,4,6]
    heap_sort(lis)
    print(lis)

三.归并类 

07归并排序

图片演示

 代码示例 

'''归并排序
思想:
1.归并排序属于归并类排序算法
2.该算法运用分治的思想
    1>分而治之  把大问题化为子问题
    2>子问题和大问题的解决方案类似
    3>存在最小子问题
    4>合并子问题的答案得到大问题的答案
3.将带排序的数据分成两个子区间
    1>左子区间 [left,mid]
    2>右子区间 [mid+1,right]
    3>分别对左右子区间运用归并排序
    4>当left == right时,即子区间中只有一个元素时,作为最小子问题(一个元素已经排好序)
    5>合并子区间:
        1)额外的存储空间 tmp =[]
        2)比较两个子区间的头元素,将小的放入tmp
        3)如果其中一个子区间的元素取完, 则将另一个子区间剩下的元素依次追加进tmp

算法分析:
1\时间复杂度:
    平均:O(nlogn)
2\空间复杂度:
    O(n)   tmp占用的空间
3\ 稳定性:
    稳定排序
4\ 精简排序:
    两个元素最多比较一次,没有重复比较'''
import random


def mergeSort(alist,left,right):
    if left == right:
        return
    else:
        mid = (left + right) //2
        mergeSort(alist,left,mid)
        mergeSort(alist,mid+1,right)
        merge(alist,left,mid,right)  # 和并

def merge(alist,left,mid,right):
    i = left
    j = mid + 1
    tmp = []
    while i <= mid and j <= right:
        if alist[i] <= alist[j]:
            tmp.append(alist[i])
            i += 1
        else:
            tmp.append(alist[j])
            j += 1
    while i <= mid:
        tmp.append(alist[i])
        i += 1
    while j <= right:
        tmp.append(alist[j])
        j += 1
    alist[left:right+1] = tmp  # 将归并的结果替换原alist中对应的元素
    
if __name__ == '__main__':
    lis = list(range(1,50,5))
    random.shuffle(lis)
    # lis = [46, 31, 6, 26, 41, 21, 11, 16, 1, 36]
    print(lis)
    mergeSort(lis,0,len(lis)-1)
    print(lis)


 四.不比较元素大小的排序

08计数排序

图片演示

 

代码示例

'''计数排序
思想:
1.不比较元素大小
2.空间换时间
3.适合数据量大,数据范围小的数据排序.
    如 年龄\成绩等
4.步骤
    1>找带排序序列的最大值k
    2>新建一个容量为k+1的计数列表count_list并将所有元素赋初值0
    3>遍历待排序序列,将遍历到的元素在count_list中对应的索引位置的值+1
        遍历完以后count_list中索引i的值j 表示i的个数为j
    4>新建结果列表
    5>遍历count_list,依次添加j个i.排序完成
算法分析:
1\时间复杂度:
    O(n+k) k为元素取值的范围
2\空间复杂度:
    O(n+k)
3\ 稳定性:
    稳定排序'''

def count_sort(alist):
    if len(alist) < 2:
        return alist
    res = []
    max_value = max(alist)
    count_list = [0] * (max_value + 1)
    for ele in alist:
        count_list[ele] += 1
    for i,j in enumerate(count_list):
        res += [i] * j
    return res

def count_sort2(alist):  # 优化计数排序
    '''确定元素的范围,减少空间浪费'''
    if len(alist) < 2:
        return alist
    res = []
    max_value = max(alist)
    min_value = min(alist)
    count_list = [0] * (max_value - min_value + 1)
    for ele in alist:
        count_list[ele - min_value] += 1
    for i,j in enumerate(count_list):
        res += [i+min_value] * j
    return res
if __name__ == '__main__':
    # lis = [2,4,3,1,5,6,7,4,5,3,2,1,2,4,6,8,9,10]
    # lis = [1]
    lis = ['a','e','z','o','f']
    print(count_sort(lis))
    print(count_sort2(lis))

09桶排序

图片演示

 

 

代码示例

'''桶排序
思想:
1.将带排序的数据按照一定的规律,分配到不同的桶中
2.对每一个桶内的元素执行其他的排序算法
3.将各子桶中有序的数据合并成一个大的有序序列,完成排序
4.桶的确定:
    1>尽可能地均匀分配
    2>空间足够的情况下,尽可能多分桶
    3>一种分桶方案>>>(max-min) //length + 1
4.步骤
    1>确定桶的数量 bucket_nums = (max_ele - min_ele)//length + 1
    2>创建桶二维列表 buckets = [[] for _  in range(bucket_nums)]
    3>分配元素进桶
    4>桶内排序
    5>合并各桶数据
算法分析:
1\时间复杂度:
    最好:O(n)
    最坏:O(n**2)
    平均:O(n+k) k为桶的个数
2\空间复杂度:
    O(n+k)
3\ 稳定性:
    稳定排序'''

def bucket_sort(alist):
    length = len(alist)
    if length < 2:
        return
    else:
        bucket_nums = (max(alist) - min(alist))//length+1  #  确定桶的数量
        buckets = [[] for _ in range(bucket_nums)]  # 创建空桶
        # 分配元素
        for ele in alist:
            buckets[(ele - min(alist))//length].append(ele)
        # print(buckets)
        # 桶内排序
        for bucket in buckets:
            bucket.sort()
        # print(buckets)
        # 合并
        index = 0
        for bucket in buckets:
            for value in bucket:
                alist[index] = value
                index += 1


if __name__ == '__main__':
    lis = [12,4,3,31,5,66,7,4,75,3,2,1111,2,4,6,8,9,210]
    bucket_sort(lis)
    print(lis)

10基数排序

图片演示

 

代码示例

'''基数排序
思想:
1.和计数\桶排序都属于分配收集排序的一种
2.不比较元素大小,用空间换时间
3.基数排序桶按排序位的可能取值来确定,如对数字的'个位'\'十位'等则选取10个桶
4.最大数字有几位,则进行几趟分配与收集,每趟趟分配与收集对某一位的数字排好序

算法分析:
1\时间复杂度:
    O(n*k) k为桶的个数
2\空间复杂度:
    O(n+k)
3\ 稳定性:
    稳定排序'''

def radix_sort(alist):
    length = len(alist)
    if length < 2:
        return
    else:
        nums = len(str(max(alist)))
        for i in range(nums):
            buckets = [[] for _ in range(10)]  # 创建空桶
            for ele in alist:  # 分配
                buckets[ele//10**i % 10].append(ele)
            # print(buckets)
            index = 0
            for bucket in buckets:  # 收集
                for value in bucket:
                    alist[index] = value
                    index += 1





if __name__ == '__main__':
    lis = [20,101,39,431,58,600,8,4,999,634,157,199,208,138,389,691,400,932,856,843,401,923]
    radix_sort(lis)
    print(lis)

五.总结

平方阶 (O(n2)) 排序 各类简单排序:直接插入、直接选择和冒泡排序。

线性对数阶 (O(nlog2n)) 排序 快速排序、堆排序和归并排序;

O(n1+§)) 排序,§ 是介于 0 和 1 之间的常数。 希尔排序

线性阶 (O(n)) 排序 基数排序,此外还有桶、箱排序。

关于稳定性

稳定的排序算法:冒泡排序、插入排序、归并排序和基数排序。

不是稳定的排序算法:选择排序、快速排序、希尔排序、堆排序。

稳定性记忆:不稳定的只有快些堆!(快速\希尔\堆),另外堆是选择排序的特例,所以再加一个选择排序


参考:

1.1.0 十大经典排序算法 | 菜鸟教程 (runoob.com)

2.dingdangcode算法学习

3.图片来源于网络


35岁学Python,也不知道为了啥?

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值