演算法 - 排序

1,插入排序(Insertion-Sort)

直接插入排序

视频素材 - bilibili
工作原理
在这里插入图片描述
代码实现

def Insertion_Sort(a):
    for i in range(len(a)):
        key = a[i]
        j = i - 1
        while i > 0 and a[i - 1] > key:
            a[i] = a[i - 1]
            i -= 1
        a[i] = key
    return a
a = [2, 3, 5, 2, 1, 9, 7, 5, 2]
b = Insertion_Sort(a)
print(b)
>>>[1, 2, 2, 2, 3, 5, 5, 7, 9]

算法分析
在这里插入图片描述
时间复杂度为
在这里插入图片描述
最佳的情况:已经由小到大排好序
第5行 则只会被执行 n-1 次
第6,7行 则不会被执行
在这里插入图片描述
最差情况:列表为逆序
第5行 对任何一个 a[i] 进行排序时 都会被执行 i 次(共有n个a[i])
第6,7行 随同 行5,每一次排序会被执行 i-1 次(进行n次排序)
在这里插入图片描述
时间复杂度【最好情况–最坏情况–平均情况】–空间复杂度–稳定性
在这里插入图片描述

折半插入排序

视频素材 - bilibili
工作原理
在这里插入图片描述
代码实现

def BinaryInsertSort(list):
    for i in range(2, len(list)):
        list[0] = list[i]
        low = 1
        high = i - 1
        while low <= high:
            m = int((low + high) / 2)  # 折半
            if list[0] < list[m]:  # 插入点在低半区
                high = m - 1
            else:  # 插入点在高半区
                low = m + 1

        j = i - 1  # 记录后移
        while j >= high + 1:
            list[j + 1] = list[j]
            j -= 1
        list[high + 1] = list[0]

**其中[0]=-1这一位置是暂存单元,不会参与排序 **

a = [-1, 2, 3, 5, 2, 1, 9, 7, 5, 2]
BinaryInsertSort(a)
print(a)

>>>[2, 1, 2, 2, 2, 3, 5, 5, 7, 9]

算法分析

与直接插入排序法相比:
折半插入减少了比较次数,但没有减少移动次数(平均性能更优)
当数据量 n 较大时,且数据越乱,越适合用折半排序
而在最佳情况下时,直接排序(只用比较一次)反而优于折半排序

在这里插入图片描述

希尔排序

视频素材 - bilibili
基本思想
在这里插入图片描述
希尔排序的算法特点:

  • 缩小增量
  • 多遍插入排序
  • 一次移动,移动位置较大,跳跃式的接近排序后的最终位置
  • 最后一次只需要少量移动
  • 增量序列必须是递减的,最后一个必须是 “1”
  • 增量序列应该是互质的(互质是公约数只有1的两个整数,叫做互质整数。)

排序思路:
增量 5 > 3 > 1在这里插入图片描述
在这里插入图片描述
代码实现

def shell_Sort(alist):
    sublistcount = len(alist) // 2  # 除2后取不超过结果的最大整数
    while sublistcount > 0:

        for startposition in range(sublistcount):
            InsertionSort(alist, startposition, sublistcount)

        print("增量:", sublistcount,
              "此时列表:", alist)

        sublistcount = sublistcount // 2  # 增量每次减小一半


def InsertionSort(alist, start, gap):
    """
    :param alist: 需要排序的列的表
    :param start: 开始位置
    :param gap: 增量
    :return: 排序后列表
    """
    for i in range(start + gap, len(alist), gap):
        currentvalue = alist[i]
        position = i

        while position >= gap and alist[position - gap] > currentvalue:
            alist[position] = alist[position - gap]
            position = position - gap

        alist[position] = currentvalue
alist = [23, 45, 11, 67, 44, 98, 69, 57, 26, 58]
shell_Sort(alist)
print(alist)
>>>增量: 5 此时列表: [23, 45, 11, 26, 44, 98, 69, 57, 67, 58]
   增量: 2 此时列表: [11, 26, 23, 45, 44, 57, 67, 58, 69, 98]
   增量: 1 此时列表: [11, 23, 26, 44, 45, 57, 58, 67, 69, 98]
   最终结果:[11, 23, 26, 44, 45, 57, 58, 67, 69, 98]

代码理解
在这里插入图片描述
算法分析
希尔排序算法效率与增量序列的取值有关
时间效率 ↓
在这里插入图片描述
希尔排序是不稳定的排序算法
在这里插入图片描述
总结
在这里插入图片描述

2,交换排序

基本思想:
两两比较,如果发生逆序则交换,直到所有记录都排好序为止。
常见的交换排序:

  • 快速排序 O(n**2)
  • 快速排序 O( nlog2^n )

冒泡排序

基于简单交换思想:
每趟不断将记录两两比较,并按照“前小后大”规则排序

举个例子:
初始:[ 21, 25, 49, 25*, 16, 08 ] n = 6
在这里插入图片描述
比较 5 次
第1趟,结束后:[ 21, 25, 25*, 16, 08, 49 ]
在这里插入图片描述
比较 4 次
第2趟,结束后:[ 21, 25, 16, 08, 25*, 49 ]
之后以此类推…
第3趟,结束后:[ 21, 16, 08, 25, 25*, 49 ] 比较3次
第4趟,结束后:[ 16, 08, 21, 25, 25*, 49 ] 比较2次
第5趟,结束后:[ 08, 16, 21, 25, 25*, 49 ] 比较1次

总结:

  • n 个记录,总共需要 n-1
  • 第 m 趟需要比较 n-m

代码实现
传送门:菜鸟教程 - 冒泡排序

(非最优版本)
def bubbleSort(arr):
    n = len(arr)

    # 遍历所有数组元素
    for i in range(n):

        # Last i elements are already in place
        for j in range(0, n - i - 1):

            if arr[j] > arr[j + 1]:
                arr[j], arr[j + 1] = arr[j + 1], arr[j]

优点:每趟结束时,不仅能挤出一个最大值到最后位置,还能同时部分理顺其他元素

那么还有没有优化空间呢?
有的,若果再一趟中,完全没有发生交换,就可以说是已经排列好,所以可以加一个 变量change 判断是否发生过交换,若没有则 break

(优化版本,增加了change判断变量)
def bubbleSort(arr):
    n = len(arr)

    # 遍历所有数组元素
    for i in range(n):
        change = False

        # Last i elements are already in place
        for j in range(0, n - i - 1):

            if arr[j] > arr[j + 1]:
                arr[j], arr[j + 1] = arr[j + 1], arr[j]
                change = True  # 发生交换
        
        # 如果未发生交换则跳出回圈
        if not change:
            break

算法分析
时间复杂度

  • 最优情况(正序)
    比较次数:n-1
    移动次数:0
  • 最坏情况(逆序)
    比较次数:Σ(n-i) = (1/2)(n^2 - n) 其中 i ←[ 1, n-1 ]
    移动次数:3Σ(n-i) = (3/2)(n^2 - n) 其中 i ←[ 1, n ]

在这里插入图片描述

快速排序(改进的交换排序)

视频素材 - bilibili
基本思想(使用递归):

  • 任取一个元素(如:第一个)为中心(pivot,枢轴

  • 所有比他的元素一律前放,比他的元素一律后放,形成左右两个子表
    -(【小】(pivot)【大】)

  • 对各子表中心选择中心元素并依此规则调整

  • 直到每个子表的元素只剩一个

快速排序演示
在这里插入图片描述
每个子表的形成都是采用从两头向中间交替式逼近法;
由于每趟中对各个子表的操作都相似,可采用递归算法

代码实现
传送门:菜鸟教程 - 快速排序

def partition(arr, low, high):
    i = (low - 1)  # 最小元素索引
    pivot = arr[high]

    for j in range(low, high):

        # 当前元素小于或等于 pivot
        if arr[j] <= pivot:
            i = i + 1
            arr[i], arr[j] = arr[j], arr[i]

    arr[i + 1], arr[high] = arr[high], arr[i + 1]
    return (i + 1)


# arr[] --> 排序数组
# low  --> 起始索引
# high  --> 结束索引

# 快速排序函数
def quickSort(arr, low, high):
    if low < high:
        pi = partition(arr, low, high)

        quickSort(arr, low, pi - 1)
        quickSort(arr, pi + 1, high)

这边要注意一下 n 的取值

n = len(list) - 1
quickSort(list, 0, n)
print(list)
>>>[2, 5, 66, 99, 77, 52, 33, 2, 1, 0, 44, 5, 10]

算法分析
时间复杂度
在这里插入图片描述
空间复杂度
在这里插入图片描述
稳定性
在这里插入图片描述
总结
在这里插入图片描述

3,选择排序(Selection Sort)

–(演算法 2,Sorting.pdf )

简单选择排序

视频素材 - bilibili
算法原理
在这里插入图片描述
精髓在于:找最小值(要注意每一次找最小值的范围)
在这里插入图片描述在这里插入图片描述
代码实现

def Normal_Select_Sort(a):
    for i in range(len(a)):
        # 找最小值的位置(index)
        key = i
        for j in range(i, len(a)):
            if a[key] > a[j]:
                key = j
        # 将最小值与a[i]进行交换
        if key != i:
            key_value = a[key]
            a[key] = a[i]
            a[i] = key_value
Normal_Select_Sort(list)
print(list)
>>>[0, 1, 2, 2, 5, 5, 10, 33, 44, 52, 66, 77, 99]

算法分析
在这里插入图片描述
时间复杂度【最好情况–最坏情况–平均情况】–空间复杂度–稳定性
在这里插入图片描述

什么是堆排序

堆的定义

视频素材 - bilibili
在这里插入图片描述
(堆实质是满足如下性质的完全二叉树二叉树中任意非叶子节点均小于(大于)它的孩子节点

光看定义肯定会比较晕,那么来看一下下边的例子吧~
在这里插入图片描述
再来看两个错误的例子
在这里插入图片描述

堆排序的原理

在这里插入图片描述
实现堆排序需要解决的两个问题:
1,如何由一个无序序列建成一个堆。
2,如何在输出堆顶元素后,调整剩余元素为新的堆。

堆的调整

视频素材 - bilibili
下边不理解的话,上边视频从3:50开始看一下
在这里插入图片描述
(大根堆 交换大者)

堆的建立

视频素材 - bilibili
通过反复的筛选,将一个无序的序列建成一个堆
在这里插入图片描述
具体的操作:
在这里插入图片描述
如果还是一脸懵逼就来看一下这个例子吧~
在这里插入图片描述

堆排序的代码实现

视频素材 - bilibili
还没学二叉树,所以下边的代码我自己看也是云里雾里,补完二叉树会回来进行更改和补充的(希望早日给这两行加上删除线)

def heapify(arr, n, i):
    largest = i
    l = 2 * i + 1  # left = 2*i + 1
    r = 2 * i + 2  # right = 2*i + 2

    if l < n and arr[i] < arr[l]:
        largest = l

    if r < n and arr[largest] < arr[r]:
        largest = r

    if largest != i:
        arr[i], arr[largest] = arr[largest], arr[i]  # 交换

        heapify(arr, n, largest)


def heapSort(arr):
    n = len(arr)

    # 建立一个最大堆
    for i in range(n, -1, -1):
        heapify(arr, n, i)

    # 一个个交换元素
    for i in range(n - 1, 0, -1):
        arr[i], arr[0] = arr[0], arr[i]  # 交换
        heapify(arr, i, 0)
heapSort(list)
print(list)
>>>[0, 1, 2, 2, 5, 5, 10, 33, 44, 52, 66, 77, 99]

算法分析

堆排序的时间主要耗费在建初始堆和调整建新堆时的反复及筛选上。
对排序的最大优点即是:最坏和最好的情况下,时间复杂度均为↓
在这里插入图片描述
无论待排序列中的记录是正序还是逆序,都不会使得堆排序处于“最好”或“最坏”的状态。
另外,堆排序仅需一个记录大小供交换用的辅助存储空间。
然而,堆排序是一种不稳定的排序方法,不适用于排序记录个数 n 较小的情况,但对于 n 较大的文件还是很有效的。

4,归并排序(Merge Sort)

视频素材 - bilibili
工作原理
将两个有序或两个以上有序的子序列,归并为一个有序序列
通常采用 2-路归并排序
即:将两个相邻的有序子序列 L[ 1 … n1+1 ] & R[ 1 … n2+1 ],归并为一个序列
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
代码实现
参考文献 — 讲的很详细

def merge(arr, l, m, r):
    n1 = m - l + 1
    n2 = r - m

    # 创建临时数组
    L = [0] * (n1)
    R = [0] * (n2)

    # 拷贝数据到临时数组 arrays L[] 和 R[]
    for i in range(0, n1):
        L[i] = arr[l + i]

    for j in range(0, n2):
        R[j] = arr[m + 1 + j]

        # 归并临时数组到 arr[l..r]
    i = 0  # 初始化第一个子数组的索引
    j = 0  # 初始化第二个子数组的索引
    k = l  # 初始归并子数组的索引

    while i < n1 and j < n2:
        if L[i] <= R[j]:
            arr[k] = L[i]
            i += 1
        else:
            arr[k] = R[j]
            j += 1
        k += 1

    # 拷贝 L[] 的保留元素
    while i < n1:
        arr[k] = L[i]
        i += 1
        k += 1

    # 拷贝 R[] 的保留元素
    while j < n2:
        arr[k] = R[j]
        j += 1
        k += 1

# 进行递归
def mergeSort(arr, start_index, end_index):
    if start_index < end_index:
        mid = int((start_index + (end_index - 1)) / 2)

        mergeSort(arr, start_index, mid)
        mergeSort(arr, mid + 1, end_index)
        
        merge(arr, start_index, mid, end_index)
arr = [12, 11, 13, 5, 6, 7]
n = len(arr)
print("给定的数组", arr)

mergeSort(arr, 0, n - 1)
print("排序后的数组", arr)
>>>给定的数组 [12, 11, 13, 5, 6, 7]
   排序后的数组 [5, 6, 7, 11, 12, 13]

算法分析
在这里插入图片描述
在这里插入图片描述
缺点:需要一个与原序列同样大小的辅助序列(L,R)

详细分析
分治算法(Divide-and-Conquer Algorithm)
在这里插入图片描述
也可以写作在这里插入图片描述

5,基数排序

视频素材 - bilibili基本思想:分配 + 收集
也叫桶排序箱排序:设置若干个箱子,将关键字为 k 的记录放入第 k 个箱子,然后再按序号将非空的链接。

**基数排序:**数字是有范围的,均由0-9这十个数字组成,则只需要设置十个箱子,相继按照个、十、百…进行排序。

举个例子说明一下:
在这里插入图片描述
第一趟的收集结果就已经将个位进行了排序,再在此结果的基础下进行第二趟。
在这里插入图片描述在这里插入图片描述
(排序完成!)

算法分析

时间效率:O(k*(n+m)) - 线性阶
k:关键字个数(有几类桶)
m:关键字取值范围为m个值(桶的个数)
(进行k趟,每趟收集m次)

再举个例子:10000人按照生日排序
年(89个桶),月(12个桶),日(31个桶)
在这里插入图片描述

6,各种排序方法比较

在这里插入图片描述

一,时间性能

1,按平均的时间性能来分,有三类排序方法:

时间复杂度为 O(nlogn) 的方法有:

  • 快速排序(最佳),堆排序和归并排序

时间复杂度为 O(n^2) 的有:

  • 直接插入排序(最佳),冒泡排序和简单选择排序

时间复杂度为 O(n) 的排序方法只有:

  • 基数排序

2,当待排记录序列按关键字顺序有序时,直接插入法冒泡排序法能达到 O(n) 的时间复杂度;而对于快速排序而言,这是最不好的情况,此时的时间复杂度退化为 O(n^2),因此是应该尽量避免的情况。

3,简单选择排序、堆排序和归并排序的时间性能不随记录序列中关键字的分布而改变。

二,空间性能

指的是排序过程中所需的辅助空间大小。

  1. 所有的简单排序方法(包括:直接插入、冒泡和简单选择)和堆排序的空间复杂度为 O(1)。
  2. 快速排序为 O(logn),为栈所需的辅助空间。
  3. 归并排序所需辅助空间最多,其空间复杂度为 O(n)。
  4. 链式基数排序需要附设队列首尾指针,则空间复杂度为 O(rd)。

三,排序方法的稳定性能

  • 稳定的排序方法指的是,对于两个关键字相等的记录,他们在序列中的相对位置,在排序的前后不会发生改变
  • 当对多关键字的记录序列进行LSD方法排序时,必须采用稳定的排序方法。
  • 对于不稳定的排序方法,只要能举出一个实例说明即可。
  • 快速排序和堆排序是不稳定的排序方法。

四,关于“排序方法的时间复杂度下线”

  • 本章讨论的各种排序方法,除基数排序,其他方法都是基于“比较关键字”进行排序的排序方法,可以证明,这类排序方法可能达到的最快的时间为 O(nlogn)。
    基数排序不是基于“比较关键字”的排序方法,所以它不受这个限制)
  • 可以用一颗判断树来描述这类基于“比较关键字”进行排序的排序方法。
发布了17 篇原创文章 · 获赞 4 · 访问量 513

猜你喜欢

转载自blog.csdn.net/weixin_46072771/article/details/105074795