【剑指Offer】金块问题——三种解法

问题描述

有一堆金块,找出质量最大的和最小的两个金块。
输入:[45, 33, 29, 68, 99, 25, 13, 14, 48, 83]
输出:[13, 99]

解题思路

本题主要目的在于考察数组的排序。这里选择三种排序方法:快速排序、归并排序和选择排序

快速排序

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

  • 从数列中挑出一个元素,称为"基准"(pivot),
  • 重新排序数列,所有元素比基准值小的摆放在基准前面,所有元素比基准值大的摆在基准的后面(相同的数可以到任一边)。在这个分区结束之后,该基准就处于数列的中间位置。这个称为分区(partition)操作。
  • 递归地(recursive)把小于基准值元素的子数列和大于基准值元素的子数列排序。递归的最底部情形,是数列的大小是零或一,也就是永远都已经被排序好了。虽然一直递归下去,但是这个算法总会结束,因为在每次的迭代(iteration)中,它至少会把一个元素摆到它最后的位置去。
    代码如下:
import random as rd


# 使用快速排序法找到最大值和最小值
# 1.挑选基准值:从数列中挑出一个元素,作为比较的"基准”
# 2.分割,即重新排序数列,以升序为例,将所有比基准值小的元素放在基准的前面,将所有比基准值大的元素放在基准的后面,如果相等,则不调整。
# 3.递归排序两个子序列,即对基准两侧的序列进行快速排序
def quickSort(left, right, array):
    if left >= right:
        return
    base = array[left]  # 以起始位置元素作为基准
    low = left  # low为序列左侧由左向右移动的游标
    high = right  # high为序列右侧从右向左移动的游标
    while low < high:
        # 如果low与high未重合,high(右侧)指向的元素大于基准元素,则high向左移动
        while low < high and array[high] >= base:
            high -= 1
        # 当high指向的元素小于基准元素时,将high指向的元素赋值到low指向的位置,因为low指向的元素已经赋值给了base,所以此时high指向的位置空出来了
        array[low] = array[high]
        # 然后移动左侧游标,如果low指向的元素小于基准元素,则low向右移动
        while low < high and array[low] <= base:
            low += 1
        # 当low指向的元素大于基准元素时,将low指向的元素赋值给high指向的位置,因为上一步已经空出了位置,所以将其赋值后,low对应的位置也空出来了
        array[high] = array[low]
    # low与high的位置重合,退出循环,此时它们指向的位置就是基准元素在最终排序后应当所在的位置,左边比它小,右边比它大
    array[low] = base
    # 对基准元素的左右两侧子序列进行递归排序
    quickSort(left, low - 1, array)
    quickSort(low + 1, right, array)


def Gold(n, G):
    quickSort(0, n - 1, G)
    print("排序后的金块质量:{}".format(G))
    return G[0], G[n - 1]


if __name__ == '__main__':
    # 1. 输入金块数量
    n = int(input("请输入金块数量:"))
    # 2. 自动生成限定范围内的金块质量g[n],假定金块质量范围为(1,100)
    gn = []
    for i in range(n):
        g = rd.randint(1, 100)
        gn.append(g)
    print("金块的质量:{}".format(gn))
    # 调用Gold方法,得到最大值和最小值
    max_gold, min_gold = Gold(n, gn)
    print("最大质量为:{}, 最小质量为:{}".format(max_gold, min_gold))

快速排序的特点:

  • 稳定性:快排是一种不稳定排序,比如基准值的前后都存在与基准值相同的元素,那么相同值就会被放在一边,这样就打乱了之前的相对顺序
  • 比较性:因为排序时元素之间需要比较,所以是比较排序
  • 时间复杂度:快排的时间复杂度为O(nlogn)
  • 空间复杂度:排序时需要另外申请空间,并且随着数列规模增大而增大,其复杂度为:O(nlogn)
  • 归并排序与快排 :归并排序与快排两种排序思想都是分而治之,但是它们分解和合并的策略不一样:归并是从中间直接将数列分成两个,而快排是比较后将小的放左边大的放右边,所以在合并的时候归并排序还是需要将两个数列重新再次排序,而快排则是直接合并不再需要排序,所以快排比归并排序更高效一些,可以从示意图中比较二者之间的区别。
  • 快速排序有一个缺点就是对于小规模的数据集性能不是很好

归并排序

归并排序(Merge sort,或mergesort),是创建在归并操作上的一种有效的排序算法。该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。
分治法:

  • 分割:递归地把当前序列平均分割成两半
  • 集成:在保持元素顺序的同时将上一步得到的子序列集成到一起(归并)。
    代码如下:
# 归并排序
def merge(left, mid, right, array):
    n1 = mid - left + 1
    n2 = right - mid
    # 创建临时数组
    L = [0] * (n1)
    R = [0] * (n2)
    # 拷贝数据到临时数组arrays L[]和R[]
    for i in range(0, n1):
        L[i] = array[left + i]
    for j in range(0, n2):
        R[j] = array[mid + 1 + j]

    # 归并临时数组到arr[left...right]
    i = 0  # 初始化第一个子数组的索引
    j = 0  # 初始化第二个子数组的索引
    k = left  # 初始归并子数组的索引
    while i < n1 and j < n2:
        if L[i] <= R[j]:
            array[k] = L[i]
            i += 1
        else:
            array[k] = R[j]
            j += 1
        k += 1
    # 拷贝L[]的保留元素
    while i < n1:
        array[k] = L[i]
        i += 1
        k += 1
    # 拷贝R[]的保留元素
    while j < n2:
        array[k] = R[j]
        j += 1
        k += 1


def mergeSort(left, right, array):
    if left < right:
        mid = int(left + (right - left) / 2)
        mergeSort(left, mid, array)
        mergeSort(mid + 1, right, array)
        merge(left, mid, right, array)

归并排序的时间复杂度为nlog(n)

选择排序

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

# 选择排序
def selectSort(array):
    for i in range(len(array)):
        min_idx = i
        for j in range(i + 1, len(array)):
            if array[min_idx] > array[j]:
                min_idx = j
        array[i], array[min_idx] = array[min_idx], array[i]
    return array

选择排序的特点:
时间复杂度:

  • 最优时间复杂度:O(n^2)
  • 最坏时间复杂度:O(n^2)

稳定性:不稳定(考虑升序每次选择最大的情况)

参考资料

  • https://blog.youkuaiyun.com/weixin_43250623/article/details/88931925
  • https://blog.youkuaiyun.com/zhaobig/article/details/78607714
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

镰刀韭菜

看在我不断努力的份上,支持我吧

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值