02-常见排序算法

关于排序算法,博客实在太多,不乏好文,图文讲述比较麻烦,一旦学会了以后,再回头来看中心思想又觉得特别简单,本博客只讲述中心思想。

大多数排序算法使用递归来写更加简洁明了,因此下面的但凡能用递归的就用递归

冒泡

最简单的一种排序算法。假设长度为n的数组arr,要按照从小到大排序。则冒泡排序的具体过程可以描述为:首先从数组的第一个元素开始到数组最后一个元素为止,对数组中相邻的两个元素进行比较,如果位于数组左端的元素大于数组右端的元素,则交换这两个元素在数组中的位置。这样操作后数组最右端的元素即为该数组中所有元素的最大值。接着对该数组除最右端的n-1个元素进行同样的操作,再接着对剩下的n-2个元素做同样的操作,直到整个数组有序排列。算法的时间复杂度为O(n^2)。

def BubbleSort(arr, end):
    if end <= 0:
        return
    i = 0
    while i < end:
        if arr[i] > arr[i + 1]:
            arr[i], arr[i + 1] = arr[i + 1], arr[i]
        i += 1

    BubbleSort(arr, end - 1)


arr = [38, 22, 14, 76, 13, 27, 49, 50, 23, 94, 81, 1, 3, 50, 99]
BubbleSort(arr, len(arr) - 1)
print(arr)

选择

选择排序则是从左往右访问过程中,始终标记最小的,一旦确认到了最小值,则将第0个值与最小值换位,依次类推,冒泡保证最后测始终最大,选择保证最左侧始终最小,不过冒泡的过程中存在交换,而选择只在确认到了最小值才进行交换,这个就效率来说,选择比较快一点。

def selectSort(arr, start):
    # print(start)
    if start >= len(arr) - 1:
        return
    k = start
    # 假设start为最小值,那么起始下标应该是start+1
    for i in range(start + 1, len(arr)):
        if arr[i] < arr[k]:
            k = i

    # 交换start和最小值
    arr[start], arr[k] = arr[k], arr[start]

    selectSort(arr, start + 1)


arr = [38, 22, 14, 76, 13, 27, 49, 50, 23, 94, 81, 1, 3, 50, 99]
selectSort(arr, 0)
print(arr)

插入排序

插入排序的核心思想是:假设左侧n个数是有序的,则将第n+1个数从右往左与前n个数进行比较,当前值比n+1大,下标继续左移,如果当前值小于等于n+1,则本轮定位结束,继续n+2的定位

def insertSort(arr, index):
    # 如果当前待确认位置的下标已经是最后一个的下一个位置,则没必要继续排序了
    if index >= len(arr):
        return
    # 如果是寻找第index的数的位置,那么index就应该与第0到第index-1个数进行比较
    i = index - 1
    while i >= 0:
        # 如果当前值小于index,则没必要继续向左插入了
        if arr[i] <= arr[i + 1]:
            break
        arr[i], arr[i + 1] = arr[i + 1], arr[i]
        i -= 1

    insertSort(arr, index + 1)


arr = [38, 22, 14, 76, 13, 27, 49, 50, 23, 94, 81, 1, 3, 50, 99]
insertSort(arr, 1)
print(arr)

快排

快速排序(Quick Sort)使用分治法策略。

它的基本思想是:通过一轮排序手段使得基准数放在了他在有序序列中的正确位置,这轮排序以后,基准数左侧的都小于基准数,基准数右侧的大于基准数。

快速排序流程:
(1) 从数列中挑出一个基准值。
(2) 将所有比基准值小的摆放在基准前面,所有比基准值大的摆在基准的后面(相同的数可以到任一边);在这个分区退出之后,该基准就处于数列的中间位置。
(3) 递归地把"基准值前面的子数列"和"基准值后面的子数列"进行排序。

重点就是如何把比基准值小的数放在左边,比基准值大的数放在右边。

以下是一层遍历,基准值归位的操作图

接下来就是对特殊情况进行判断,从而形成完整的代码

def quickSortInfo(arr, start, end):
    # 如果没有元素或者只有一个元素,可以直接返回
    if start >= end:
        return

    # 如果是两个元素,则需要判断下这两个元素的大小关系
    if end - start == 1:
        if arr[start] > arr[end]:
            arr[start], arr[end] = arr[end], arr[start]
        return

    k = start  # 基准值坐标
    i = start + 1  # 从左往右遍历,找出所有的小于基准值
    j = end  # 从右往左遍历,找出所有的大于基准值的数

    # 只要i,j没有相遇,则循环不会结束,直至找出本轮递归中的基准值的准确位置
    while i < j:
        # 只要元素i小于基准值,则一直右移
        while i != j and arr[k] > arr[i]:
            i += 1

        # 将k与i-1互换位置
        arr[k], arr[i - 1] = arr[i - 1], arr[k]

        # 修改k,k已经改变了
        k = i - 1

        # 只要元素j小于基准值,则一直左移
        while i < j and arr[k] < arr[j]:
            j -= 1

        # 假如说ij相遇了,那本轮递归就结束了。ij相遇需要判断,
        if i == j:
            if arr[k] > arr[i]:
                arr[k], arr[i] = arr[i], arr[k]
                k += 1
            break
        # 如果ij没有相遇,则kij必定是231的形式,需要换一下位置
        else:
            arr[k], arr[i], arr[j] = arr[j], arr[k], arr[i]

        k += 1
        i += 1
        j -= 1

    # 基准值左侧继续递归
    quickSortInfo(arr, start, k - 1)
    # 基准值右侧继续递归
    quickSortInfo(arr, k + 1, end)


arr = [38, 22, 14, 76, 13, 27, 49, 50, 23, 94, 81, 1, 3, 50, 99]
quickSortInfo(arr, 0, len(arr) - 1)
print(arr)

堆排序

大根堆:一棵完全二叉树,满足任一节点都比其孩子节点大
小根堆:一棵完全二叉树,满足任一节点都比其孩子节点小

此处我们使用大根堆来进行正序排序。

可以看到:
0号元素,左右子节点分别是 2*0+1 2*0+2
1号元素,左右子节点分别是 2*1+1 2*1+2
2号元素,左节点是 2*2+1

因此可以依照此关系,把一个连续数组当作一个完全二叉树。于是就有了如下的堆排序代码。核心思路虽然简单,但是细枝末节却是最麻烦的,下面的代码初看未必看得懂,需要多看几次,我觉得这份代码应该是我能写出来的最高校的堆排序代码

# 该函数用来寻找当前堆的最大值下标
def heapSortInfo(arr, i, length):
    # 当前下标小于长度,但是左节点下标又超过了最大下标
    # 说明该元素是根节点,直接返回当前下标
    if i < length and 2 * i + 1 >= length:
        return i
    # 走到了这里,已经说明该元素的父节点存在左节点,
    # 但是右节点不存在的话,就返回左节点的下标
    # 完全二叉树最多只存在一个没有右节点的节点,因此下面的访问条件只会触发一次
    if i >= length:
        return i - 1

    # 取出左子树的最大值下标
    left = heapSortInfo(arr, 2 * i + 1, length)
    # 取出右子树的最大值下标
    right = heapSortInfo(arr, 2 * i + 2, length)
    # 三者比较得最最大值下标
    max = i if arr[i] > arr[left] else left
    max = max if arr[max] > arr[right] else right

    return max


def heapSort(arr):
    length = len(arr)
    # 只要待排序的元素数量超过1个,就要进行排序
    while length >= 2:
        # 获取最大的元素下标
        max = heapSortInfo(arr, 0, length)
        # 将最大元素放到数组末尾
        arr[length - 1], arr[max] = arr[max], arr[length - 1]
        length -= 1


arr = [38, 22, 14, 76, 13, 27, 49, 50, 23, 94, 81, 1, 3, 50, 19]
heapSort(arr)
print(arr)

归并排序

归并排序的基本思路如下图所示:

import random

def merge(li, low, mid, high):
    # 一次归并
    '''
    :param li: 列表
    :param low: 起始位置
    :param mid: 按照那个位置分
    :param high: 最后位置
    :return:
    '''
    i = low
    j = mid + 1
    ltmp = []
    while i <= mid and j <= high:
        if li[i] <= li[j]:
            ltmp.append(li[i])
            i += 1
        else:
            ltmp.append(li[j])
            j += 1
    while i <= mid:
        ltmp.append(li[i])
        i += 1
    while j <= high:
        ltmp.append(li[j])
        j += 1
    li[low:high + 1] = ltmp


def _merge_sort(li, low, high):
    if low < high:  # 至少两个元素
        mid = (low + high) // 2
        _merge_sort(li, low, mid)
        _merge_sort(li, mid + 1, high)
        merge(li, low, mid, high)
        print(li[low:high + 1])


def merge_sort(li):
    return _merge_sort(li, 0, len(li) - 1)


li = list(range(16))
random.shuffle(li)
print(li)
merge_sort(li)
print(li)
posted @   yaowy  阅读(20)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 全程不用写代码,我用AI程序员写了一个飞机大战
· DeepSeek 开源周回顾「GitHub 热点速览」
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 记一次.NET内存居高不下排查解决与启示
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
点击右上角即可分享
微信分享提示