常见排序算法详解(冒泡、选择、插入、快速、希尔、归并)

一、排序算法

1、冒泡排序(Bubble Sort)

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

原理:

  • 比较相邻的元素。如果第一个比第二个大(升序),就交换他们两个。
  • 对每一对相邻元素作同样的工作,从开始第一对到结尾的最后一对。这步做完后,最后的元素会是最大的数。
  • 针对所有的元素重复以上的步骤,除了最后一个。
  • 持续每次对越来越少的元素重复上面的步骤,直到没有任何一对数字需要比较。
list1 = [12, 54, 23, 56, 67, 45, 1]

def bubbleSort():
    '''冒泡排序'''
    for i in range(len(list1) - 1, 0, -1):
        for j in range(i):
            if list1[j] > list1[j + 1]:
                list1[j], list1[j + 1] = list1[j + 1], list1[j]
        print(list1)

bubbleSort()

时间复杂度:

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

效果图:

 

 

 2、选择排序

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

list1 = [2, 6, 9, 5, 3, 1]

def selection_sort(list1):
    n = len(list1)
    # 需要进行n-1次选择操作
    for i in range(n - 1):
        # 记录最小位置
        min_pos = i
        # 从i+1位置到末尾选择出最小数据
        for j in range(i + 1, n):
            if list1[j] < list1[min_pos]:
                min_pos = j
        # 如果选择出的数据不在正确位置,进行交换
        if min_pos != i:
            list1[i], list1[min_pos] = list1[min_pos], list1[i]

        print('----》', list1)

selection_sort(list1)

时间复杂度:

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

效果图:

 

3、插入排序

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

list1 = [3, 2, 9, 5, 1, 0]


def insert_sort(list1):
    '''插入排序'''
    # 从第二个位置,即下标为1的元素开始向前插入
    for i in range(1, len(list1)):
        # 从第i个元素开始向前比较,如果小于前一个元素,交换位置
        for j in range(i, 0, -1):
            if list1[j] < list1[j - 1]:
                list1[j], list1[j - 1] = list1[j - 1], list1[j]
        print(list1)


insert_sort(list1)

时间复杂度:

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

 效果图:

 

4、快速排序

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

步骤为:

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

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

list1 = [89, 56, 34, 16, 98, 110, 78, 90]


def quik_sort(list1, start, end):
    '''快速排序'''
    # 递归退出的条件
    if start >= end:
        return
    
    # 设定起始元素要寻找位置的基准标准
    mid = list1[start]
    # low为序列左边的由左向右移动的游标
    low = start
    # high为序列左边的由右向左移动的游标
    high = end

    while low < high:
        # 若low与high未重合high指向的元素不比基准元素小,则high向左移动
        while low < high and list1[high] >= mid:
            high -= 1
        list1[low] = list1[high]
        # 若low与high未重合low指向的元素不比基准元素小,则low向左移动
        while low < high and list1[low] < mid:
            low += 1
        list1[high] = list1[low]
    
    # 退出循环后,low与high重合,此时所指位置为基准元素的正确位置
    # 将基准元素放到该位置
    list1[low] = mid
    
    # 左边序列快速排序  递归
    quik_sort(list1, start, low - 1)
    # 右边序列快速排序
    quik_sort(list1, low + 1, end)
    print(list1)


quik_sort(list1, 0, len(list1) - 1)

时间复杂读:

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

演示:

 

 

 5、希尔排序

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

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

list1 = [23, 17, 77, 54, 12, 43, 65, 45]

def shell_sort(list1):
    '''插入排序'''
    n = len(list1)

    # 初始化步长
    gap = n // 2
    while gap > 0:
        # 按步长进行插入排序
        for i in range(gap, n):
            j = i
            while j >= gap and list1[j - gap] > list1[j]:
                list1[j - gap], list1[j] = list1[j], list1[j - gap]
                j -= gap

        gap = gap // 2
        print(list1)

shell_sort(list1)

 

 时间复杂度:

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

演示:

 

6、归并排序

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

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

原理:

def merge_sort(list1):
    '''归并排序'''
    if len(list1) <= 1:
        return list1
    # 二分分解
    num = len(list1) // 2
    left = merge_sort(list1[:num])
    right = merge_sort(list1[num:])
    print(left)
    print(right)
    # 进行合并
    return merge(left, right)


def merge(left, right):
    '''合并操作,将两个有序数组left[]  right[]合并成一个大的有序数组'''
    # 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


list1 = [12, 34, 21, 56, 43, 67]
a = merge_sort(list1)
print(a)

 

 时间复杂度:

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

 

 6种排序算法比较:

 

posted @ 2019-08-21 21:03  刚--  阅读(2083)  评论(0编辑  收藏  举报