排序算法
如何分析一个“排序算法”?
学习排序算法,我们除了学习它的算法原理、代码实现之外,更重要的是要学会如何评价、分析一个排序算法。那分析一个排序算法,要从哪几个方面入手呢?
- 排序算法的执行效率
对于排序算法执行效率的分析,我们一般会从这几个方面来衡量:
- 时间复杂度
- 比较次数和交换(或移动)次数
-
排序算法的内存消耗
原地排序算法,就是特指空间复杂度是 O(1) 的排序算法 -
排序算法的稳定性
仅仅用执行效率和内存消耗来衡量排序算法的好坏是不够的。针对排序算法,我们还有一个重要的度量指标,稳定性。这个概念是说,如果待排序的序列中存在值相等的元素,经过排序之后,相等元素之间原有的先后顺序不变
递归
递归是一种应用非常广泛的算法(或者编程技巧)
需要三个条件
- 分解成几个子问题
- 循环条件
- 基线条件
# 用递归的方式输出l=['jack',('tom',23),'rose',(14,55,67)] 列表内的每一个元素
# jack tom 23 rose 14 55 67
l = ['jack', ('tom', 23), 27, (14, 55, 67)]
def dp(l):
if isinstance(l, (str, int)):
print(l)
else:
for i in l: #先进入这里的循环
dp(i)
dp(l)
一. 冒泡排序
需要重复对序列进行遍历, 两个两个相邻的数进行比较,若前面的比后面的数大, 则把他们的位置调换过来., 直到把最大的数放到最后,需要比较的次数会逐渐减少
图片演示
代码演示
li = [54, 26, 93, 17, 77, 31, 44, 55, 20]
索引0 1 2 3 4 5 6 7 8
def bu_sort(li):
n = len(li)
for j in range(n-1, 0, -1): #每次需要比较的次数. 8 7 6 5 4 3 2 1
for i in range(j):
if li[i] > li[i+1]:
li[i], li[i+1] = li[i+1], li[i]
if __name__ == '__main__':
li = [54, 26, 93, 17, 77, 31, 44, 55, 20]
bu_sort(li)
print(li)
冒泡排序是稳定的, 因为两个相同的数不会进行交换, 时间复杂度分析: 若序列本来就是有序的, 时间复杂度是O(n)
二. 选择排序(Selection Sort)
概念: 首先在未排序序列中找到最小(大)元素,存放到排序序列的起始位置,然后,再从剩余未排序元素中继续寻找最小(大)元素,然后放到已排序序列的末尾。以此类推,直到所有元素均排序完毕。
通俗说:分为已排和为排, 从未排序的那里找出一个最大或者最小, 找最小就放在最前, 以此类推, 次数逐渐减少
交换时是两个进行交换, 仅仅两个位置交换而已
选择排序演示
实现
def select_sort(li):
n = len(li)
for i in range(n-1): #操作的次数 len(li)-1
min_index = i 这是第一个值
for j in range(min_index, n): #未排序的下标
if li[j] < li[min_index]:
min_index = j #把最小的值的索引给第一个最小的索引
li[i], li[min_index] = li[min_index], li[i] #若可以进入这里, 就交换
li = [54, 26, 93, 17, 77, 31, 44, 55, 20]
select_sort(li)
print(li)
稳定性:最小值重复稳定, 最大值重复不稳定
时间复杂度n^2
三. 插入排序(Insertion Sort)
概念: 先构建一个有序的序列, 第一个就为有序的序列, 然后从后面的无序的序列中拿出第一个与前面的有序序列进行比较, 插入符合的位置
需要两个操作:一个是与前面的比较, 一个是移动前面的给后面的补上
演示如下:
代码实现
def insert_sort(li):
n = len(li)
for i in range(n-1, 0, -1):
for j in range(i):
if li[j] > li[i]: #i 是先为8所以表示后面
li[j], li[i] = li[i], li[j]
if __name__ == '__main__':
li = [54, 26, 93, 17, 77, 31, 44, 55, 20]
insert_sort(li)
print(li)
时间复杂度是O(n^2)
是稳定的
四. 希尔排序(Shell_sort)
按照一定的步长选出几组,然后再进行第一次组合,再按照一定的步长取出,步长一般是长度的一半直到步长为1才可以完成排序
希尔排序是把记录按下标的一定增量分组,对每组使用直接插入排序算法排序;随着增量逐渐减少,每组包含的关键词越来越多,当增量减至1时,整个文件恰被分成一组,算法便终止
def shell_sort(li):
n = len(li)
gap = n // 2
while gap > 0:
for i in range(gap, n): # 4-8
while i >= gap and li[i - gap] > li[i]:
li[i - gap], li[i] = li[i], li[i - gap]
i -= gap
gap = gap // 2 #更新gap, 逐渐减小
if __name__ == '__main__':
li = [54, 26, 93, 17, 77, 31, 44, 55, 20]
shell_sort(li)
print(li)
时间复杂度
不稳定
五. 快速排序(Quick_sort)
快速排序(英语:Quicksort),又称划分交换排序(partition-exchange sort)
定义: 首先设定一个基准指, 然后比较后面的值, 若比基准值小放到基准值前面, 比基准值大放到基准值后面
然后利用递归重复上面的操作, 这排序是不稳定的
29就是基准值
快速排序的实现(老师)
def quick_sort(alist, start, end):
"""快速排序"""
# 递归的退出条件
if start >= end:
return
# 设定起始元素为要寻找位置的基准元素
mid = alist[start]
# low为序列左边的由左向右移动的游标
low = start
# high为序列右边的由右向左移动的游标
high = end
while low < high:
# 如果low与high未重合,high指向的元素不比基准元素小,则high向左移动
while low < high and alist[high] >= mid:
high -= 1
# 将high指向的元素放到low的位置上
alist[low] = alist[high]
# 如果low与high未重合,low指向的元素比基准元素小,则low向右移动
while low < high and alist[low] < mid:
low += 1
# 将low指向的元素放到high的位置上
alist[high] = alist[low]
# 退出循环后,low与high重合,此时所指位置为基准元素的正确位置
# 将基准元素放到该位置
alist[low] = mid
# 对基准元素左边的子序列进行快速排序
quick_sort(alist, start, low-1)
# 对基准元素右边的子序列进行快速排序
quick_sort(alist, low+1, end)
alist = [54,26,93,17,77,31,44,55,20]
quick_sort(alist,0,len(alist)-1)
print(alist)
自己的
def quick_sort(li, strat, end):
low = strat #为了递归那里方便控制
higt = end
mid = li[low] #设定一个基准值, 一般是第一个
if strat >= end #退出条件, 递归一定要有退出条件
return
while low < higt: #两个游标重合, 退出循环
while low < higt and li[higt] > mid:
higt -= 1
li[low] = li[higt] #将比中间值小的值放到左边
while low < higt and li[low] < mid:
low += 1
li[higt] = li[low] #将比中间值大的值放到右边
li[low] = mid #退出这个循环, 两个游标重合,将中间值传给游标指定的值
quick_sort(li, strat, low-1) #递归, 将中间值前面的值再来重复上面的操作
quick_sort(li, low+1, end) #递归, 将中间值后面的值再来重复上面的操作
if __name__ == '__main__':
li = [54, 26, 93, 17, 77, 31, 44, 55, 20]
quick_sort(li, 0, len(li)-1)
print(li)
六. 归并排序(Merge sort)
先对一组数进行拆分, 按中间位置进行分, 分到不能再分,然后按原来的步骤排序和起来,将数组分解最小之后,然后合并两个有序数组,基本思路是比较两个数组的最前面的数,谁小就先取谁,取了后相应的指针就往后移一位。然后再比较,直至一个数组为空,最后把另一个数组的剩余部分复制过来即可
代码实现
归并排序的实现(老师)
def merge_sort(li):
# 如果列表长度小于1,不在继续拆分
if len(li) <= 1:
return li
# 二分分解
mid_index = len(li) // 2
left = merge_sort(li[:mid_index])
right = merge_sort(li[mid_index:])
# 合并
return merge(left,right)
def merge(left, right):
'''合并操作,将两个有序数组left[]和right[]合并成一个大的有序数组'''
# left与right的下标指针
l_index, r_index = 0, 0
result = []
while l_index < len(left) and r_index < len(right):
if left[l_index] < right[r_index]:
result.append(left[l_index])
l_index += 1
else:
result.append(right[r_index])
r_index += 1
result += left[l_index:]
result += right[r_index:]
return result
alist = [54,26,93,17,77,31,44,55,20]
sorted_alist = merge_sort(alist)
print(sorted_alist)
我的
"""
归并排序:对半拆分序列,拆到不能拆为止,然后比较大小合并
"""
def merge_sort(li):
mid_index = len(li)//2#对半分
if len(li) <= 1: #递归退出条件
return li
left = merge_sort(li[:mid_index])
right = merge_sort(li[mid_index:])
left_index, right_index = 0, 0
result = []
while left_index < len(left) and right_index < len(right): #一次长度只是1 , 就是两个数, 一个左一个右
if left[left_index] < right[right_index]:#进入这里是左的小于右边的直接添加进去
result.append(left[left_index])
left_index += 1
else:
result.append(right[right_index]) #如果左边的数比较大, 就添加右边的数
right_index += 1
result += left[left_index:]
result += right[right_index:]
return result
if __name__ == '__main__':
li = [54, 26, 93, 17, 77, 31, 44, 55, 20]
sort = merge_sort(li)
print(sort)