问题描述
有一堆金块,找出质量最大的和最小的两个金块。
输入:[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