主要介绍下几种排序算法,顺便复习一下:
1. 冒泡排序
①算法介绍:
对数据项两两相邻比较,通过N-1次循环后,得出结果
②算法代码:
def bubblesort(alist):
flag = True
len_list = len(alist)-1
while flag and len_list:
for x in range(len_list):
if alist[x+1] < alist[x]:
tmp = alist[x]
alist[x] = alist[x+1]
alist[x+1] = tmp
len_list -= 1
return alist
③算法分析:
算法过程需要N-1次循环,比对次数由N-1逐次降低至1,每次循环中发生数据项比对。比对次数总和为N^2/2-N/2, 即比对 时间复杂度O(N^2)
每次循环包括赋值与交换操作。交换时间复杂度:O(N2):其中交换次数最少为0(顺序列表);最差为每次比对都要交换值O(N2)
(缺点:时间复杂度要求高)
无需额外空间去存储。只有一个tmp
(优点,空间复杂度要求低)
2. 选择排序
①算法介绍:
在每次循环中选择最大项位置,放入最后一项,进行N-1次循环
②算法代码:
def select_sort(alist):
len_list = len(alist)-1
for i in range(len_list, 0, -1):
maxfix = 0
for x in range(1, i+1):
if alist[x] > alist[maxfix]:
maxfix = x
tmp = alist[i]
alist[i] = alist[maxfix]
alist[maxfix] = tmp
return alist
③算法分析:
与冒泡排序相同,算法过程需要N-1次循环,比对次数由N-1逐次降低至1,每次循环中发生数据项比对。比对次数总和为N^2/2-N/2, 即比对时间复杂度仍是O(N^2)
每次循环包括赋值交换操作。相比冒泡,因为每次最多只涉及一次交换,时间复杂度仅为O(N)
无需额外空间去存储。只有一个tmp
(优点,空间复杂度要求低)
3. 插入排序
①算法介绍:
在每次循环中,将前面已排好序的列表项与第N项进行比对,找到插入合适的位置,完成排序。
②算法代码:
def insert_sort(alist):
for i in range(1, len(alist)):
pos = i
val = alist[i]
while pos > 0 and alist[pos-1] > val:
alist[pos] = alist[pos-1]
pos -= 1
alist[pos] = val
③算法分析:
算法过程依旧需要N-1次循环,每次循环发生数据比对。
最差情况下每次循环 都需要 比对及插入 当前列表中 所有的数据项,时间复杂度为O(N^2)
最好情况下,即列表已是有序,只需要一次比对,时间复杂度为O(N)
但相比冒泡与选择排序,由于只有一次赋值,性能稍微优于前两者。但时间复杂度相同。
4. 谢尔排序
①算法介绍:
对列表间隔进行插入排序,循环每次间隔从N/2逐步降低间隔为1(1就是前面的插入排序),每次循环中,对每个数进行间隔排序。
②算法代码:
def shell_sort(alist):
sublist = len(alist) // 2
while sublist > 0:
for start_pos in range(sublist):
insert_sort(alist, start_pos, sublist)
# 固定间隔后,间隔中的每一项的循环调用插入排序,起点从第一项开始,间隔sublist长度
sublist = sublist // 2
# 然后减小间隔,最终完成排序
def insert_sort(alist, start, gap):
for i in range(start + gap, len(alist), gap):
pos = i
val = alist[i]
while pos >= gap and alist[pos - gap] > val:
alist[pos] = alist[pos - gap]
pos -= gap
alist[pos] = val
③算法分析:
可以减少很多插入排序中的无效比对,时间复杂度约为O(N^1.5)
5. 归并排序
①算法介绍:
将数据表分割为两个子列表,将子列表进行归并,子列表排好序后,并归并。
采用递归算法:
- 基本结束条件为列表长度为1(列表不能再被分割)
- 缩小规模(列表进行二分分割)
- 调用自身(每次分割后的子列表再调用自身进行排序)
②算法代码:
def merge_sort(alist):
if len(alist) <= 1:
return alist
# 循环结束条件
half_len = len(alist) // 2
left_list = merge_sort(alist[0:half_len])
right_list = merge_sort(alist[half_len:])
# 调用自身
mergelist = []
while left_list and right_list:
if left_list[0] <= right_list[0]:
mergelist.append(left_list.pop(0))
else:
mergelist.append(right_list.pop(0))
mergelist.extend(left_list if left_list else right_list)
# 子列表再排序融合
return mergelist
③算法分析:
可分为两个部分:分裂与合并。
二分分裂,时间复杂度为O(logN)
合并,由于对子列表每一下进行对比,时间复杂度为O(N)
总体时间复杂度为O(NlogN)
注意到空间复杂度由于需要额外1倍的列表进行储存,所以空间复杂度较大。
6. 快速排序
①算法介绍:
通过设立一个中值,将列表中比中值大的,放在中值右边列表;比中值小的,放在中值左边列表。这样可以分为子列表,通过递归算法,得出最终结果。
![](https://img.haomeiwen.com/i153965/8040058440861b9f.png)
②算法介绍:
def partition(alist, first, end):
mid_val = alist[first]
left_flag = first + 1
right_flag = end
done = False
while not done:
while left_flag <= right_flag and alist[left_flag] <= mid_val:
left_flag += 1
# 先设定左边标志,直到左边标志确定以后,再循环右边标志
# 两个条件的位置不能反,因为left flag可以超过列表长度
while right_flag >= left_flag and alist[right_flag] >= mid_val:
right_flag -= 1
# 等号防止出现左右标记指向同一个值,出现无限循环
if left_flag > right_flag:
done = True
# 只有在左边标志小于右边标志的情况下才交换
else:
tmp = alist[left_flag]
alist[left_flag] = alist[right_flag]
alist[right_flag] = tmp
tmp = alist[first]
alist[first] = alist[right_flag]
alist[right_flag] = tmp
return right_flag
def quick__sort(alist, first, end):
if first < end:
# 循环结束条件
mid_list = partition(alist, first, end)
# 分割点确定
quick__sort(alist, first, mid_list - 1)
# 将分割点左边部分递归
quick__sort(alist, mid_list + 1, end)
# 分割点右边递归
def quick_sort(alist):
quick__sort(alist, 0, len(alist) - 1)
# 用两个函数的原因在于用一个函数,初始的起始值与末尾初始条件无法在递归中设定
alist = eval(input())
quick_sort(alist)
print(alist)
当然,如果不在乎空间复杂度的话,也可以写成如下格式:
def quick_sort(b):
"""快速排序"""
if len(b) < 2:
return b
# 选取基准,随便选哪个都可以,选中间的便于理解
mid = b[len(b) // 2]
# 定义基准值左右两个数列
left, right = [], []
# 从原始数组中移除基准值
b.remove(mid)
for item in b:
# 大于基准值放右边
if item >= mid:
right.append(item)
else:
# 小于基准值放左边
left.append(item)
# 使用迭代进行比较
left_list = quick_sort(left)
right_list = quick_sort(right)
return left_list+[mid]+right_list
③算法分析:
算法过程包括分裂与移动;
如果中值能每次将列表分裂为相等的部分,分裂复杂度依旧为O(logN);
但如果中值选择偏离中间值,使得两边数据极为不对等,则分裂复杂度甚至可以达到O(N)
移动复杂度在于两边比较数据,由于每次移动中,都需要比较每项与中间值大小,所以复杂度为O(N);
总体复杂度为O(NlogN)~O(N^2)
另外可以不需要额外的存储空间
网友评论