【数据结构与算法】排序问题

目录

冒泡排序 O(n^2)

选择排序 O(n^2)

插入排序 O(n^2)

快速排序 O(nlogn)

堆排序 O(nlogn)

归并排序 O(nlogn)

希尔排序

计数排序 O(n)

基数排序


冒泡排序 O(n^2)

列表每两个相邻的数,如果前面比后面大,则交换这两个数;(每趟排序把最大的数推到无序区的最后面,成为有序区的第一个数,指针最终停在无序区的倒数第二个数)

一趟排序完成后,则无序区减少一个数,有序区增加一个数。(共需要N-1趟)

def bubble_sort(li):
    for i in range(len(li) - 1):  # i表示第i趟
        exchange = False  # 标志位
        for j in range(len(li) - i - 1):
            if li[j] > li[j + 1]:  # 降序排列将这里改为小于号
                li[j], li[j + 1] = li[j + 1], li[j]  # 交换两个元素
                exchange = True
        if not exchange:  # 如果一趟结束没有元素交换,结束流程
            return

exchange是标志位,当一趟结束后没有发生交换则说明已经排序完成,不需要继续循环

选择排序 O(n^2)

每趟遍历找到最小的数放在前面/后面的有序区,代码把有序区放到列表最前面

def select_sort_simple(li):
    li_new = []
    for i in range(len(li)):
        min_val = min(li)
        li_new.append(min_val)
        li.remove(min_val)
    return li_new

def select_sort(li):
    for i in range(len(li) - 1):  # i是第i趟
        min_loc = i  # 标记为无序区第一个位置
        for j in range(i + 1, len(li)):
            if li[j] < li[min_loc]:
                min_loc = j
        li[i], li[min_loc] = li[min_loc], li[i]  # 与无序区第一个位置交换
    return li

插入排序 O(n^2)

(插牌)初始时有序区只有一个数,每次从无序区取出一个数,插到有序区的正确位置

def insert_sort(li):
    for i in range(1, len(li)):  # i表示摸到的牌的下标,第一个数自动归入有序区不需要遍历
        tmp = li[i]
        j = i - 1  # j指的是手里的牌的下标
        while j >= 0 and li[j] > tmp:
            li[j + 1] = li[j]  # 下标为j的牌右移一位
            j -= 1  # 下标j左移一位
        li[j + 1] = tmp  # 当手里的牌没有摸到的牌大时,把摸到的牌放在该张牌右边

快速排序 O(nlogn)

归位:当p左边的数字都比p小,右边的数字都比p大,成为p归位完成

1. 首先取一个元素p使p归位,最开始left指针指向列表第一个数,right指针指向列表最后一个数,当left<right时,运行partition函数。将列表第一个数存放到tmp中,如果right指针指向的数比tmp大,right指针向左走一位,直到right指针指向的数比tmp小,此时把right指针指向的数放到left指针的位置;如果left指针指向的数比tmp小,则left指针向右走一位,直到left指针指向的数比tmp大,把left指针指向的数放到right指针的位置(这个位置的数之前被放到了没有移动前的left指针的位置),以此类推。当left指针和right指针相遇时结束循环,把tmp放到此时两个指针共同指向的位置上。

2. 第一个元素归位后位置为mid,递归mid左右两个子列表完成排序。

在这个过程中相当于先把left位置的元素取到tmp中,right指针找到一个比tmp小的元素a放到left的位置,left指针再找到一个比tmp大的元素b放到right的位置,最后把tmp填回两个指针共同指向的位置。

def quick_sort(li, left, right):
    if left < right:  # 至少两个元素
        mid = partition(li, left, right)
        quick_sort(li, left, mid - 1)
        quick_sort(li, mid + 1, right)


def partition(li, left, right):
    tmp = li[left]
    while left < right:  # 循环终止条件是left和right的指针碰到一起
        while left < right and li[right] >= tmp:  # 右边的数大于等于tmp的话往右移一位
            right -= 1  # 最终right指向一个小于tmp的数
        li[left] = li[right]  # 小于tmp的数放到左边
        while left < right and li[left] <= tmp:
            left += 1
        li[right] = li[left]
    li[left] = tmp  # 把tmp归位
    return left

quick_sort(li, 0, len(li) - 1)

堆排序 O(nlogn)

1. 建立堆

2. 得到堆顶元素为最大元素

3. 去掉堆顶,将堆最后一个元素放到堆顶,通过一次调整使堆重新有序

4. 堆顶元素为第二大元素

5. 重复以上步骤直到堆变空

也就是说在已经有了一个堆结构的前提下进行排序。

堆:(大根堆)任意节点比其孩子节点大

def sift(li, low, high):
    """
    调整父节点和子节点之间的大小关系(不递归)
    :param li:
    :param low: 根节点的位置
    :param high: 堆的最后一个元素的位置
    :return:
    """
    i = low  # 指向根节点
    j = 2 * i + 1  # 指向下一层的左孩子节点
    tmp = li[low]  # 存储堆顶
    while j <= high:  # 只要j位置有节点,即可以循环
        if j + 1 <= high and li[j + 1] > li[j]:  # 右孩子比左孩子大
            j = j + 1  # j指向右孩子
        if li[j] > tmp:  # 上一个if是挑选孩子中更大的一个与父节点比较
            li[i] = li[j]
            i = j  # 移到下一层
            j = 2 * i + 1
        else:  # tmp更大,把tmp放到i的位置上
            break
    li[i] = tmp  # 循环结束(因为j位置没有节点或者tmp更大)后把tmp放回去


def heap_sort(li):
    n = len(li)
    for i in range((n - 2) // 2, -1, -1):  # 从最后一个非叶节点开始反向遍历
        sift(li, i, n - 1)
    # 构造堆完成了
    for i in range(n - 1, -1, -1):  # li中i以后的节点为有序区,避免更高的空间复杂度
        li[0], li[i] = li[i], li[0]
        sift(li, 0, i - 1)

 另外可以通过内置模块import heapq,使用heapify(x)构造堆,使用heappush(heap,item)将item推入堆元素,使用heappop(heap)从小到大输出堆元素

topk问题解决思路:

1. 取列表前k个元素建立一个小根堆,堆顶就是目前第k大的数

2. 遍历原列表,对于列表中的元素,小于堆顶则忽略该元素,大于堆顶则将堆顶更换为该元素,然后进行一次调整

3. 遍历列表所有元素后倒序弹出堆顶

def topk(li, k):
    heap = li[0:k]
    for i in range((k - 2) // 2, -1, -1):
        sift(heap, i, k-1)
    # 构建堆完成了
    for i in range(k, len(li) - 1):
        if li[i] > heap[0]:
            heap[0] = li[i]
            sift(heap, 0, k-1)
    # 遍历列表所有元素
    for i in range(k-1, -1, -1):
        heap[0], heap[i] = heap[i], heap[0]
        sift(heap, 0, i-1)
    # 出数
    return heap

归并排序 O(nlogn)

假设目前列表分两段有序,将其合并为一个有序列表称为一次归并。将列表分解成元素,递归合并两个有序列表。

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)


def merge(li, low, mid, high):
    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执行完有一部分没数了
    while i <= mid:
        ltmp.append(li[i])
        i += 1
    while j <= high:
        ltmp.append(li[j])
        j += 1
    li[low:high + 1] = ltmp

希尔排序

分组插入排序算法

1. 取一个整数d1 = n/2,将元素分为d1个组,每组相邻元素之间距离为d1,在组内进行直接插入排序;

2. 取第二个整数d2 = d1/2,重复上述分组排序过程,直到di = 1,即所有元素在同一组内进行直接插入排序。

希尔排序每趟并不使某些元素有序,而是使整体数据越来越接近有序,最后一趟排序使得所有数据有序。时间复杂度比较复杂,和选取的gap序列有关。

def insert_sort_gap(li, gap):
    for i in range(gap, len(li)):  # i表示摸到的牌的下标
        tmp = li[i]
        j = i - gap  # j指的是手里的牌的下标
        while j >= 0 and li[j] > tmp:
            li[j + gap] = li[j]  # 下标为j的牌右移gap位
            j -= gap  # 下标j左移gap位
        li[j + gap] = tmp  # 当手里的牌没有摸到的牌大时,把摸到的牌放在该张牌右边


def shell_sort(li):
    d = len(li) // 2
    while d >= 1:
        insert_sort_gap(li, d)
        d //= 2

计数排序 O(n)

已知列表中的数范围在0到100之间

def count_sort(li, max_count=100):
    count = [0 for _ in range(max_count + 1)]
    for val in li:  # val是序列里的值
        count[val] += 1
    li.clear()
    for val, ind in enumerate(count):  # 值为val的元素有ind个
        for i in range(ind):
            li.append(val)

桶排序(当数据范围比较大的时候对计数排序的改进)

def bucket_sort(li, n=100, max_num=10000):
    buckets = [[] for _ in range(n)]  # 创建桶
    for val in li:
        i = min(val // (max_num // n), n - 1)  # i表示val放到几号桶里
        buckets[i].append(val)
        # 可以全都放到桶里再排序,也可以放到桶里的过程中顺便排序
        # 这里选第二种
        for j in range(len(buckets[i]) - 1, 0, -1):  # 从后往前遍历
            if buckets[i][j] < buckets[i][j - 1]:
                buckets[i][j], buckets[i][j - 1] = buckets[i][j - 1], buckets[i][j]
            else:
                break
    sorted_li = []
    for buc in buckets:
        sorted_li.extend(buc)  # 连接列表
    return sorted_li

基数排序

利用多关键字排序思想,先比较数字的最高位(本质上还是桶排序)

def radix_sort(li):
    max_num = max(li) # 最大值的位数
    it = 0 # 迭代次数
    while 10 ** it <= max_num:
        buckets = [[] for _ in range(10)]
        for val in li:
            digit = (val // 10 ** it) % 10
            buckets[digit].append(val)
        # 分桶完成
        li = []
        for buc in buckets:
            li.extend(buc)
        # 把数写回li
        it += 1
    return li

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值