排序

数据结构与算法 专栏收录该内容
1 篇文章 0 订阅

排序

排序:将一组无序的记录调整为有序记录的过程

列表排序:将无序的列表调整为有序的列表

  • 输入:无序列表
  • 输出:有序列表(升序或者降序)

python内置的排序函数:sort()

常见的排序算法

Low B排序算法

冒泡排序(Bubble Sort)

  • 比较列表每两个相邻的数, 如果前面的比后面的大,则交换两者

  • 一趟排序完毕,无序区减少一个数,有序去区增加一个数

  • 代码实现关键点:排序趟数,和无序区域范围

# 原版冒泡排序代码
from typing import List

def bubble_sort(values: List[int]) -> List[int]:
    """
    冒泡排序算法实现
    Args:
        values:
    Returns:
    """
    for i in range(len(values) - 1):  # 排序趟数
        for j in range(len(values) - 1 - i):  # 遍历无序区的数进行交换
            if values[j] > values[j + 1]:
                values[j], values[j + 1] = values[j + 1], values[j]
        print(f"第{i + 1}趟: {values}")
    return values
  • 冒泡排序代码改进:如果在一趟排序过程中未发生交换,这说明列表已有序,可终止排序返回结果
# 改进版冒泡排序代码
from typing import List

def bubble_sort(values: List[int]) -> List[int]:
    """
    冒泡排序算法实现
    Args:
        values:
    Returns:
    """
    for i in range(len(values) - 1):  # 排序趟数
        exchange = False  # 是否已排序完毕,如果一趟不发生交换,则表明列表已排序完毕
        for j in range(len(values) - 1 - i):  # 遍历无序区的数进行交换
            if values[j] > values[j + 1]:
                values[j], values[j + 1] = values[j + 1], values[j]
                exchange = True
        if not exchange:  # 检查列表是否已经有序
            return values
        print(f"第{i + 1}趟: {values}")
    return values

选择排序

原理:假设列表ls长度为n,遍历列表n - 1次,第i次(i从0到n-1)遍历找到索引i到n-1的最小值的索引min_idx,然后交换ls[i] 与ls[min_idx]的值

def select_sort(values):
    for i in range(len(values) - 1):  # 第i趟
        min_idx = i
        for j in range(i + 1, len(values)):  # 遍历无序区
            if values[j] < values[min_idx]:  # 寻找无序区最小值的索引
                min_idx = j
        values[i], values[min_dix] = values[min_dix], values[i]  # 交换无序区的初始索引和最小值索引的值

时间复杂度:O( n 2 n^2 n2)

插入排序

原理

  • 列表values,长度为n

  • 将无序区的第一个元素插入到有序区中,保证有序区任然有序。n-1趟之后列表就变得有序

python实现:

  • 方式1:

    def insert_sort(values: List):
        for i in range(1, len(values)):  # 无序区的起始索引从1到len(values)-1
            # 遍历有序区将无序区收个元素插入到有序区中,保证有序区任然有序
            for j in range(i):
                if values[i] < values[j]:  # 如果无序区收个元素小于有序区索引为j的元素,则应将该元素插入有序区j处
                    values.insert(j, values.pop(i))
                    break
    
  • 方式2

    def insert_sort_v2(values: List):
        for i in range(1, len(values)): # 第i趟,i为无序区收个元素的索引
            # 查找在有序区的插入位置
            item = values[i]
            j = i - 1  # 无序区的最后一个元素索引
            while item < values[j] and j >= 0:
                values[j + 1] = values[j]
                j -= 1
            values[j + 1] = item  # 将item插入有序区
    

时间复杂度:O( n 2 n^2 n2)

NB排序算法

快速排序

原理: 在列表中选取一个基准元素,将小于基准的元素放在基准左侧,反之将大于基准的元素放在基准右侧。在基准的左半区间和右半区间进行递归调用。

步骤

  1. 列表values, 初始左右边界索引left =0 ,right =len(values) - 1
  2. 传入参数:列表values和左右索引边界left和right, 将values[left]作为基准, 暂存于mid_value
  3. 将right从右向左移动(递减),直到找到第一个小于基准的数,left和right索引处元素交换。将left从左向右移动,直到找到第一个大于基准的数,交换left和right索引处元素。
  4. 重复执行步骤3, 直到不满足left < right为止。
  5. 将所用为left处元素赋值为mid_value, 返回left或者right
  6. 在基准的左半区间values[left: mid-1]和右半区间[mid + 1, right]递归执行2-5步(递归参数分别为 values, left ,mid-1 和values, mid + 1, right),直到满足递归停止条件。递归停止条件 left >= right(待排序子列表元素少用两个)

时间复杂度 O ( n l o g n ) O(nlogn) O(nlogn)

python实现

def partition(values: List[int], left: int, right: int) -> int:
    mid_value = values[left]
    while left < right:
        # 从右向左寻找第一个小于基准的数,放在left所指位置
        while values[right] >= mid_value and left < right:
            right -= 1
        values[left] = values[right]
        # 从左向右寻找第一个大于基准的数,放在right所指位置
        while values[left] <= mid_value and left < right:
            left += 1
        values[right] = values[left]
    values[left] = mid_value

    return left

def quick_sort(values: List[int], left: int, right: int):
    if left < right: # 判断当前列表元素是否大于1
        # 选择第一个元素作为基准,将大于基准的元素放在右侧,小于基准的元素放在左侧,返回基准的索引
        mid = partition(values, left, right)
        # 递归调用
        quick_sort(values, left, mid - 1)  # 对左版区间
        quick_sort(values, mid + 1, right)  # 对右半区间

快速排序算法的问题

  • 快速排序设计递归调用,如果使用python实现,注意不能超过python的最大递归深度(可以修改)
  • 快速排序的最坏情况(列表降序排列),时间复杂度为 O ( n 2 ) O(n^2) O(n2)

堆排序

树的相关概念

在这里插入图片描述

  • 树的定义:一个集合有n个节点, 如果n=0,则是一棵空树,如果n>0, 则树有一个根节点,剩余的n-1个节点组成m个集合,每个集合也是一棵树
  • 根节点,子节点
  • 输的高度(深度):树的最大层数, 上图的树的深度为4
  • 树的度:树的节点中包含最多子节点的节点的子节点数量, 上图的树的度为6
  • 孩子节点、父亲节点: 上图中B是A的子节点,A是B的父节点
  • 子树:上图中EIJPQ是一个子树
二叉树

什么是二叉树?

数的度小于等于2的树,称为二叉树

在这里插入图片描述
满二叉树:二叉树的每一层的节点都达到最大值称为满二叉树

在这里插入图片描述

完全二叉树:叶子节点只能出现在最下层和次下层,且最下层的叶子节点从右到左排列,不会出现空缺

在这里插入图片描述

二叉树的存储:

  • 链式存储

  • 顺序存储

在这里插入图片描述

  • 父节点和孩子节点的索引关系:
    • 父亲节点索引为 i i i, 左孩子节点索引为 2 i + 1 2i+1 2i+1, 右孩子节点索引为 2 i + 2 2i+2 2i+2
    • 孩子节点为 i i i, 父亲节点索引为 ( i − 1 ) / / 2 (i-1)//2 (i1)//2

什么是堆?

  • 堆是一种特殊的完全二叉树结构

    • 大根堆:任一节点都比孩子节点大

    • 小根堆:任一节点都比孩子节点小

在这里插入图片描述

堆的向下调整属性

  • 节点的左右子树都是堆,单自身不是堆

在这里插入图片描述

  • 通过向下调整将上面的树调整为一个大根堆

    • 步骤:

      • 取出根节点2,空出根节点, 比较下层的节点9和7的大小,将大的节点9放到根节点位置,空出原来9的节点

      • 由于2小于8或者5,所以将8(8大于5)移动到空节点,原来节点8的位置变为空

      • 由于2小于6或者4, 所以将6(6大于4)移动到空节点,原来节点6的位置变为空

      • 由于空节点为叶子节点,所以将2放在空节点处

使用堆的向下调整完成堆排序

堆排序步骤

  1. 建立大根堆(降序)
  2. 获得堆顶元素(最大值)
  3. 去掉堆顶,将堆最后一个元素放在堆顶,然后通过一次向下调整使堆重新有序
  4. 堆顶元素为第二大元素
  5. 重复步骤3,直到堆为空

如何构造堆?

在这里插入图片描述

步骤:

  1. 先让最后一个非叶子节点作为根节点子树变得有序
  2. 再让倒数第二个非叶子节点作为根节点的子树变得有序
  3. 以此类推,从后向前使得整个树变得有序

堆排序python实现

def sift(values, low, high):
    """
    堆的向下调整函数
    Args:
        values: 表示堆的数组
        low: 堆的根节点索引
        high: 堆的最后一个元素的索引
    Returns:
    """
    temp = values[low]  # 暂存堆顶元素
    i = low
    j = 2 * i + 1  # 左孩子节点索引
    while j <= high:  # 存在左孩子节点
        if j + 1 <= high and values[j] < values[j + 1]:  # 存在右孩子节点且右孩子节点大于左孩子节点
            j += 1  # 将j指向右孩子节点
        if temp < values[j]:  # 子孩子节点大于父亲节点
            values[i] = values[j]  # 将子孩子节点放在父节点位置
            # 更新i和j, 下移一层
            i = j
            j = 2 * i + 1
        else:  # 子孩子节点小于父亲节点,则找到合适位置,插入元素,退出循环
            values[i] = temp
            break
    else:  # 如果顺利退出循环,需要将temp放入叶子节点
        values[i] = temp


def heap_sort(values):
    """
    堆排序实现
    Args:
        values: 列表
    Returns:
    """
    # 建立堆(大根堆)
    high = len(values) - 1  # 最后一个叶子节点索引
    for i in range((high - 1) // 2, -1, -1):  # 从后向前遍历非叶子节点
        sift(values, i, high)  # 可以将high作为向下调整的最后一个叶子节点的索引
    # 挨个取出数字
    for i in range(high, -1, -1):
        values[0], values[i] = values[i], values[0]  # 堆顶和堆最后一个叶子节点交换
        sift(values, 0, i - 1)  # 堆向下调整

堆排序时间复杂度 n l o g ( n ) nlog(n) nlog(n)

堆排序 解决topk问题

topk问题:取出一个集合(n个元素)中前k大的数,k<n

解决思路

  1. 先排序后切片, 时间复杂度 O ( n l o g n ) O(nlogn) O(nlogn)
  2. 冒泡排序,排序k趟, 时间复杂度 O ( k n ) O(kn) O(kn)
  3. 堆排序思路,时间复杂度 O ( n l o g k ) O(nlogk) O(nlogk)
    • 取出列表中前k个元素建立一个小根堆
    • 遍历列表中剩余的n-k个元素,如果当前元素比堆顶元素大,则将堆顶替换为该元素,然后进行一次向下调整,否则丢弃该元素
def sift_down(values: List, low: int, high: int):
    """
    堆向下调整实现
    Args:
        values:
    Returns:
    """
    temp = values[low]
    i = low
    j = 2 * i + 1
    while j <= high:
        # 若i节点存在右孩子节点且小于左孩子节点
        if j + 1 <= high and values[j] > values[j + 1]:
            j += 1  # j指向有孩子节点
        if temp > values[j]:
            values[i] = values[j]
            i = j
            j = 2 * i + 1
        else:
            values[i] = temp
            break
    else:
        values[i] = temp


def topk_heap(values: List, k: int) -> List:
    k_vlaues = values[:k]
    res_values = values[k:]

    # 取出列表的前k个元素构建小根堆
    end = (k - 1 - 1) // 2  # 小根堆最后一个非叶子节点索引
    # 取出列表的前k个元素构建小根堆
    for i in range(end, 0, -1):
        sift_down(k_vlaues, i, k - 1)
    # 依次取出列表中剩余元素与堆堆顶元素比较,若大于堆顶元素,则替换堆顶元素,否则跳过
    for item in res_values:
        if item > k_vlaues[0]:
            k_vlaues[0] = item
            sift_down(k_vlaues, 0, k - 1)
    # 从小根堆末尾一次弹出所有元素
    for i in range(k - 1, -1, -1):
        k_vlaues[0], k_vlaues[i] = k_vlaues[i], k_vlaues[0]  # 交换堆顶和末尾元素
        sift_down(k_vlaues, 0, i - 1)  # 向下调整

    return k_vlaues

归并排序

归并

若果一个列表示分段有序的([1, 3, 5, 7, 2, 4, 6, 8]),则可以通过一次归并, 使得整个列表变得有序

归并排序

分解:将列表进行分解,直到每个子列表只有一个元素

终止条件: 子列表之后一个元素,一个元素是有序的

合并:将两个有序列表合并,直到合并索引的子列表

在这里插入图片描述

python实现归并排序

import random
from typing import List


def merge(values: List, low: int, high: int, mid: int):
    """
    一次归并实现将两段有序列表变得有序
    Args:
        values: 两段有序列表
        mid: 第一段有序列表的最后一个元素的索引
    Returns:
    """
    i = low  # 第一段有序列表的起始索引
    j = mid + 1  # 第二段有序列表的起始索引

    ordered_values = []

    while i <= mid and j <= high:
        if values[i] < values[j]:
            ordered_values.append(values[i])
            i += 1
        else:
            ordered_values.append(values[j])
            j += 1
    else:  # 将两段剩余的元素加入结果列表
        while i <= mid:  # 将第一段有序列表的剩余元素加入结果列表
            ordered_values.append(values[i])
            i += 1
        while j <= high:  # 将第二段有序列表剩余元素加入结果列表
            ordered_values.append(values[j])
            j += 1
        # 另一种实现
        # ordered_values.extend(values[i:mid + 1])
        # ordered_values.extend(values[j:high + 1])
    values[low:high + 1] = ordered_values


def merge_sort(values: List, low: int, high):
    """
    归并排序实现:
    1. 将左半段列表进行归并排序,变得有序
    2. 将you半段列表进行递归排序, 变得有序
    3. 合并左右半段数组
    4. 递归调用1-3步
    递归终止条件: 列表少于两个元素
    Args:
        values: 列表
        low: 起始索引
        high: 结束索引
    Returns:
    """
    if low < high:  # 列表至少有两个元素
        mid = (low + high) // 2
        merge_sort(values, low, mid)
        merge_sort(values, mid + 1, high)
        merge(values, low, high, mid)
        print(values[low:high + 1])


if __name__ == "__main__":
    values = list(range(8))
    random.shuffle(values)
    print(values)
    merge_sort(values, 0, len(values) - 1)
    print(values)

递归排序时间复杂度 O ( n l o g n ) O(nlogn) O(nlogn)

python内置的排序算法sort()是基于归并排序实现的

快排、堆排序和归并排序比较

  • 三种排排序算法的时间复杂度都是 O ( n l o g ) O(nlog) O(nlog)
  • 一般情况下,就运行速度而言:快速排序 < 归并排序 < 堆排序
  • 三种排序算法的缺点:
    • 快速排序在极端情况下效率低
    • 归并排序需要额外的内存消耗
    • 堆排序在相对快的排序算法中较慢

在这里插入图片描述

其他排序算法

  • 希尔排序

  • 计数排序

  • 基数排序

  • 0
    点赞
  • 0
    评论
  • 0
    收藏
  • 一键三连
    一键三连
  • 扫一扫,分享海报

©️2021 CSDN 皮肤主题: 深蓝海洋 设计师:CSDN官方博客 返回首页
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值