Python实现几大排序算法的原理和实现

目录

冒泡排序

选择排序

插入排序

快速排序

希尔排序

归并排序

堆排序



 

 

这里用Python实现了简单的7种排序,还有三种可以参考下面文章

https://www.cnblogs.com/onepixel/p/7674659.html 

https://www.jianshu.com/p/7d037c332a9d

https://blog.csdn.net/shan9liang/article/details/7555811

 

冒泡排序

冒泡排序(英语:Bubble Sort)是一种简单的排序算法。它重复地遍历要排序的数列,一次比较两个元素,如果他们的顺序错误就把他们交换过来。遍历数列的工作是重复地进行直到没有再需要交换,也就是说该数列已经排序完成。这个算法的名字由来是因为越小的元素会经由交换慢慢“浮”到数列的顶端。

冒泡排序算法的运作如下:

  • 比较相邻的元素。如果第一个比第二个大(升序),就交换他们两个。
  • 对每一对相邻元素作同样的工作,从开始第一对到结尾的最后一对。这步做完后,最后的元素会是最大的数。
  • 针对所有的元素重复以上的步骤,除了最后一个。(n-1)
  • 持续每次对越来越少的元素重复上面的步骤,直到没有任何一对数字需要比较。
def bubble_sort(list):
    for i in range(0, len(list)-1):
        for j in range(0, len(list)-1-i):
            if list[j] > list[j+1]:
                list[j], list[j+1] = list[j+1], list[j]


if __name__ == "__main__":

    li = [54, 26, 93, 17, 77, 31, 44, 55, 20]
    bubble_sort(li)
    print(li)

    li2 = [54, 226, 93, 17, 77, 31, 44, 55, 20]
    bubble_sort(li2)
    print(li2)
    
# /Users/mintou/Desktop/PYCharm/venv/bin/python /Users/mintou/Desktop/PYCharm/01-test.py
# [17, 20, 26, 31, 44, 54, 55, 77, 93]
# [17, 20, 31, 44, 54, 55, 77, 93, 226]
# 
# Process finished with exit code 0

 

时间复杂度

  • 最优时间复杂度:O(n) (表示遍历一次发现没有任何可以交换的元素,排序结束。)
  • 最坏时间复杂度:O(n2)
  • 稳定性:稳定

 

选择排序

 

选择排序(Selection sort)是一种简单直观的排序算法。它的工作原理如下。首先在未排序序列中找到最小(大)元素,存放到排序序列的起始位置,然后,再从剩余未排序元素中继续寻找最小(大)元素,然后放到已排序序列的末尾。以此类推,直到所有元素均排序完毕。

def selection_sort(list):
    n = len(list)
    for i in range(0,n-1):
        min_index = i
        for j in range(i+1,n):
            if list[j] < list[min_index]:
                min_index = j
        if i != min_index:
            list[i], list[min_index] = list[min_index], list[i]


if __name__ == "__main__":

    li = [54, 26, 93, 17, 22, 31, 44, 55, 20]
    selection_sort(li)
    print(li)

    li2 = [54, 226, 93, 17, 77, 31, 100, 55, 20]
    selection_sort(li2)
    print(li2)
# 
# /Users/mintou/Desktop/PYCharm/venv/bin/python /Users/mintou/Desktop/PYCharm/01-test.py
# [17, 20, 22, 26, 31, 44, 54, 55, 93]
# [17, 20, 31, 54, 55, 77, 93, 100, 226]
# 
# Process finished with exit code 0

红色表示当前最小值,黄色表示已排序序列,蓝色表示当前位置。

外层遍历n-1,每次都选择list头部位子默认是最小值,然后内存循环从外层的i+1开始比较,通过临时变量记录最小值小标,然后和每一次的list头元素比较,index不同,就交换,排序从左到右,从小到大,左边不变,右边进行排序比较

时间复杂度

  • 最优时间复杂度:O(n2)
  • 最坏时间复杂度:O(n2)
  • 稳定性:不稳定(考虑升序每次选择最大的情况)

 

插入排序

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

插入排序和上面的选择排序是差不多的,插入操作的是左边的有序队列,而选择操作的是右边的无序队列


def insert_sort(list):
    n = len(list)
    for i in range(1,n):
        for j in range(i,0,-1):
            if list[j] < list[j-1]:
                list[j], list[j-1] = list[j-1], list[j]



if __name__ == "__main__":

    li = [54, 26, 93, 17, 22, 31, 44, 55, 20]
    insert_sort(li)
    print(li)

    li2 = [54, 226, 93, 17, 77, 31, 100, 55, 20]
    insert_sort(li2)
    print(li2)

# /Users/mintou/Desktop/PYCharm/venv/bin/python /Users/mintou/Desktop/PYCharm/01-test.py
# [17, 20, 22, 26, 31, 44, 54, 55, 93]
# [17, 20, 31, 54, 55, 77, 93, 100, 226]
# 
# Process finished with exit code 0

 

时间复杂度

  • 最优时间复杂度:O(n) (升序排列,序列已经处于升序状态)
  • 最坏时间复杂度:O(n2)
  • 稳定性:稳定

 

 

快速排序

快速排序(英语:Quicksort),又称划分交换排序(partition-exchange sort),通过一趟排序将要排序的数据分割成独立的两部分,其中一部分的所有数据都比另外一部分的所有数据都要小,然后再按此方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行,以此达到整个数据变成有序序列。

步骤为:

  1. 从数列中挑出一个元素,称为"基准"(pivot),
  2. 重新排序数列,所有元素比基准值小的摆放在基准前面,所有元素比基准值大的摆在基准的后面(相同的数可以到任一边)。在这个分区结束之后,该基准就处于数列的中间位置。这个称为分区(partition)操作。
  3. 递归地(recursive)把小于基准值元素的子数列和大于基准值元素的子数列排序。

递归的最底部情形,是数列的大小是零或一,也就是永远都已经被排序好了。虽然一直递归下去,但是这个算法总会结束,因为在每次的迭代(iteration)中,它至少会把一个元素摆到它最后的位置去。

def quick_sort(list, start, end):

    if start >= end:
        return

    low = start
    high = end
    mid = list[start]

    while low < high:
        while low < high and list[high] >= mid:
            high -= 1
        list[low] = list[high]

        while low < high and list[low] < mid:
            low += 1
        list[high] = list[low]

    list[low] = mid

    quick_sort(list, start, low-1)
    quick_sort(list, low+1, end)


if __name__ == "__main__":

    li = [54, 26, 93, 17, 22, 31, 44, 55, 20]
    quick_sort(li,0,len(li)-1)
    print(li)

    li2 = [54, 226, 93, 17, 77, 31, 100, 55, 20]
    quick_sort(li2,0,len(li)-1)
    print(li2)

# /Users/mintou/Desktop/PYCharm/venv/bin/python /Users/mintou/Desktop/PYCharm/01-test.py
# [17, 20, 22, 26, 31, 44, 54, 55, 93]
# [17, 20, 31, 54, 55, 77, 93, 100, 226]
# 
# Process finished with exit code 0

 

  • 最优时间复杂度:O(nlogn)
  • 最坏时间复杂度:O(n2)
  • 稳定性:不稳定

从一开始快速排序平均需要花费O(n log n)时间的描述并不明显。但是不难观察到的是分区运算,数组的元素都会在每次循环中走访过一次,使用O(n)的时间。在使用结合(concatenation)的版本中,这项运算也是O(n)。

在最好的情况,每次我们运行一次分区,我们会把一个数列分为两个几近相等的片段。这个意思就是每次递归调用处理一半大小的数列。因此,在到达大小为一的数列前,我们只要作log n次嵌套的调用。这个意思就是调用树的深度是O(log n)。但是在同一层次结构的两个程序调用中,不会处理到原来数列的相同部分;因此,程序调用的每一层次结构总共全部仅需要O(n)的时间(每个调用有某些共同的额外耗费,但是因为在每一层次结构仅仅只有O(n)个调用,这些被归纳在O(n)系数中)。结果是这个算法仅需使用O(n log n)时间。

 

希尔排序

希尔排序(Shell Sort)是插入排序的一种。也称缩小增量排序,是直接插入排序算法的一种更高效的改进版本。希尔排序是非稳定排序算法。该方法因DL.Shell于1959年提出而得名。 希尔排序是把记录按下标的一定增量分组,对每组使用直接插入排序算法排序;随着增量逐渐减少,每组包含的关键词越来越多,当增量减至1时,整个文件恰被分成一组,算法便终止。

希尔排序过程

希尔排序的基本思想是:将数组列在一个表中并对列分别进行插入排序,重复这过程,不过每次用更长的列(步长更长了,列数更少了)来进行。最后整个表就只有一列了。将数组转换至表是为了更好地理解这算法,算法本身还是使用数组进行排序。

例如,假设有这样一组数[ 13 14 94 33 82 25 59 94 65 23 45 27 73 25 39 10 ],如果我们以步长为5开始进行排序,我们可以通过将这列表放在有5列的表中来更好地描述算法,这样他们就应该看起来是这样(竖着的元素是步长组成):

13 14 94 33 82
25 59 94 65 23
45 27 73 25 39
10

然后我们对每列进行排序:

10 14 73 25 23
13 27 94 33 39
25 59 94 65 82
45

将上述四行数字,依序接在一起时我们得到:[ 10 14 73 25 23 13 27 94 33 39 25 59 94 65 82 45 ]。这时10已经移至正确位置了,然后再以3为步长进行排序:

10 14 73
25 23 13
27 94 33
39 25 59
94 65 82
45

排序之后变为:

10 14 13
25 23 33
27 25 59
39 65 73
45 94 82
94

最后以1步长进行排序(此时就是简单的插入排序了)

def shell_sort(list):
    n = len(list)
    gap = n // 2
    while gap > 0:
        for i in range(gap, n):
            for j in range(i, 0, -gap):
                if list[j] < list[j - gap]:
                    list[j], list[j - gap] = list[j - gap], list[j]
        gap //= 2


if __name__ == "__main__":

    li = [54, 26, 93, 17, 22, 31, 44, 55, 20]
    shell_sort(li)
    print(li)
    li2 = [54, 226, 93, 17, 77, 31, 100, 55, 20]
    shell_sort(li2)
    print(li2)
    
    
# /Users/mintou/Desktop/PYCharm/venv/bin/python /Users/mintou/Desktop/PYCharm/01-test.py
# [17, 20, 22, 26, 31, 44, 54, 55, 93]
# [17, 20, 31, 54, 55, 77, 93, 100, 226]
# 
# Process finished with exit code 0

时间复杂度

  • 最优时间复杂度:根据步长序列的不同而不同
  • 最坏时间复杂度:O(n2)
  • 稳定想:不稳定

归并排序

归并排序是采用分治法的一个非常典型的应用。归并排序的思想就是先递归分解数组,再合并数组。

将数组分解最小之后,然后合并两个有序数组,基本思路是比较两个数组的最前面的数,谁小就先取谁,取了后相应的指针就往后移一位。然后再比较,直至一个数组为空,最后把另一个数组的剩余部分复制过来即可。

def merge_sort(list):
    if len(list) <= 1:
        return list
    # 二分分解
    num = len(list)//2
    left = merge_sort(list[:num])
    right = merge_sort(list[num:])
    # 合并
    return merge(left, right)


def merge(left, right):
    l, r = 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 += left[l:]
    result += right[r:]
    return result


if __name__ == "__main__":

    li = [54, 26, 93, 17, 22, 31, 44, 55, 20]
    print(merge_sort(li))
    print("原始数组"+str(li))
    li2 = [54, 226, 93, 17, 77, 31, 100, 55, 20]
    print(merge_sort(li2))
    print("原始数据"+str(li2))

# /Users/mintou/Desktop/PYCharm/venv/bin/python /Users/mintou/Desktop/PYCharm/01-test.py
# [17, 20, 22, 26, 31, 44, 54, 55, 93]
# 原始数组[54, 26, 93, 17, 22, 31, 44, 55, 20]
# [17, 20, 31, 54, 55, 77, 93, 100, 226]
# 原始数据[54, 226, 93, 17, 77, 31, 100, 55, 20]
# 
# Process finished with exit code 0

时间复杂度

  • 最优时间复杂度:O(nlogn)
  • 最坏时间复杂度:O(nlogn)
  • 稳定性:稳定

堆排序

堆的概念
堆:本质是一种数组对象。特别重要的一点性质:<b>任意的叶子节点小于(或大于)它所有的父节点</b>。对此,又分为大顶堆(这里来做排序)和小顶堆(计算top k算法问题),大顶堆要求节点的元素都要大于其孩子,小顶堆要求节点元素都小于其左右孩子,两者对左右孩子的大小关系不做任何要求。
利用堆排序,就是基于大顶堆或者小顶堆的一种排序方法。下面,我们通过大顶堆来实现。

步骤1:建立大顶堆  参数1 list  参数2 建堆list长度   建堆需要调整的节点

如下序列  1  3  4  2  7  9  8  10  16  14  建立大顶堆后,这里可以看下面的代码理解,无非就是通过循环把让根节点一定是当先序列的最大值

 

步骤2:取出大顶堆根节点和序列最后的元素进行交换

交换之后,序列最后的值肯定是最大值,但是此时这个堆就不再是我们的大顶堆了

 

步骤3:交换一次,序列长度动态调整为n-1(操作一次就-1)

继续执行建堆代码,把n-1的序列调整为大顶堆

 

步骤4:循环步骤2和步骤3即可

def heap_sort(list):
    # 构建大顶堆,保证最大值位于叶节点,并且父节点的值大于子节点
    build_max_heap(list)

    # 当前堆中序列长度 初始化为序列的长度
    i = len(list)

    # 每次取出堆顶元素置于序列的最后
    # 然后修改大顶堆的长度,重新调整堆,这里length持续减少,i由于是头部换到尾部,我们传0进去就可以重新建堆
    while i > 0:
        list[i - 1], list[0] = list[0], list[i - 1]
        i -= 1
        # 这里由于已经在开始的时候建了大顶堆,每次拿出根节点最大值后不需要循环建堆,只要调整根节点0下标下修改的节点
        adjust_max_heap(list, i, 0)

# 构建大顶堆
def build_max_heap(list):
    length = len(list)
    # 第一次根据无序序列,建立大顶堆需要循环进行多次建堆  多个节点需要调整,就要循环全部
    for num in range((length-1) // 2,-1,-1):
        adjust_max_heap(list,length,num)


# 调整大顶堆
#list:待调整序列 length: 序列长度 i:需要调整的结点
def adjust_max_heap(list, length, i):
    # 定义一个int值保存当前最大值的下标
    largest = i
    # 执行循环操作  1、寻找最大值下标 2、最大值与父节点交换
    while 1:
        # 获取左右子节点的下标
        left, right = LEFT(i), RIGHT(i)
        # 当前左子节点的下标小于序列长度,并且左叶子节点值大于父节点,把最大值下标赋值
        if left < length and list[left] > list[i]:
            largest = left
        else:
            largest = i

        if right < length and list[right] > list[largest]:
            largest = right
        # 如果值不等,说明当前节点的父节点不是最大值,需要交换值
        if largest != i:
            list[i], list[largest] = list[largest], list[i]
            # 这里交换之后,需要把交换的子节点重新作为调整节点进行堆调整
            i = largest
            continue
        else:
            break

# 左节点下标
def LEFT(i):
    return 2*i + 1
# 右节点下标
def RIGHT(i):
    return 2*i + 2

 

 

 

 

 

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值