1.排序算法的稳定性:
原有的序列中,两个元素同一经过排序后位置即使调换也符合排序要求,那么如果排序之后这两个元素保持原有的顺序,算法就是稳定的
3,1,1,2
排序后:
1,1,2,3
前两个1的顺序和原序列中的顺序一样,那么就是稳定的,也可以以元组排序为例,只对元组中的第一个元素排序,那么第二个元素不影响排序,如果两个元组第一个元素相同,第二个不同,那么这两个元组的前后顺序应该与排序前一致
2.冒泡排序:
思路:
每次比较当前元素和下一个元素的大小,如果当前的大,就和下一个交换位置,这样遍历完一遍后,最大的元素会在最末端,然后对剩下的元素再进行遍历,找出第二大的,以此类推
为了方便,这里用顺序表
def bubble_sort(alist):
for j in range(len(alist)-1):
for i in range(len(alist)-1-j):
if alist[i]>alist[i+1]:
alist[i], alist[i+1] = alist[i+1], alist[i]
如果某一次从头走到尾没有发生交换操作,说明此时序列已经是有序的了,因此可以改进
需要一个记录是否交换的count
def bubble_sort(alist):
for j in range(len(alist)-1):
count = 0
for i in range(len(alist)-1-j):
if alist[i]>alist[i+1]:
alist[i], alist[i+1] = alist[i+1], alist[i]
count += 1
if count == 0:
break # 或者return
最优时间复杂度:,本身就是排序好的
最坏时间复杂度:,遇到的刚好是倒叙的序列
稳定性:稳定
3.选择排序:
每次把序列中最小的放到前面去
第一次找到最小的,让他和第一个位置的数据交换,第二次从第一个之后开始找,找到最小的把他和第二个未知的数据交换
def select_sort(alist):
"""选择排序"""
# 写代码先写内部描述,也就是核心操作
# 在写外部循环
# 结合外部循环修改参数
n = len(alist)
for j in range(0, n-1):
min_index = j
for i in range(j+1, n):
if alist[min_index] > alist[i]:
min_index = i
alist[j], alist[min_index] = alist[min_index], alist[j]
if __name__ == "__main__":
li = [54,26,93,17,77,31,44,55,20]
print(li)
select_sort(li)
print(li)
计算复杂度:
外循环:n-1次
内层循环:接近n次
所以时间复杂度:,最优与最坏一样久
稳定性:不稳定(考虑升序每次选择最大而不是最小的情况)
4.插入排序:
插入排序和选择排序的区别:插入排序是对前面已经排好序的序列进行操作,不断往里面加入新的元素,选择排序是对后面未排序的序列进行操作
def insert_sort(alist):
"""插入排序"""
for j in range(1, len(alist)): # 从列表(从零开始)的第一个元素到最后一个元素
i = j # i代表内存循环的起始值
# 执行从右边的无序序列中的第一个元素,即i位置的元素,然后将其插入到前面的正确位置中
while i > 0:
if alist[i] < alist[i-1]:
alist[i], alist[i-1] = alist[i-1], alist[i]
i -= 1
else:
break # 前面的都比他小,这里直接跳出,不用等循环结束
最优时间复杂度:,本身就是排序好的
最坏时间复杂度:最坏
稳定性:稳定
5.希尔排序:
插入排序变种,有一个gap,每隔一个gap取一个元素,这些元素构成一个子序列
gap是几就有几个子序列,再对每个子序列进行插入排序,插入排序结束后,再调小gap,再进行插入排序
只到最后gap=1
如果gap一开始就是1,那么就相当于插入排序
# coding:utf-8
def shell_sort(alist):
"""希尔排序"""
n = len(alist)
gap = n // 2
# gap = gap // 2
while gap > 0:
for j in range(gap,n):
i = j
while i > 0:
if alist[i] < alist[i-gap]:
alist[i], alist[i-gap] = alist[i-gap], alist[i]
i -= gap
else:
break
gap = gap // 2
最优时间复杂度:
最坏时间复杂度:最坏
平均时间复杂度:
稳定性:不稳定
6.快速排序:
双指针,从两路进行排序,思路是找到目前排序元素应该在序列中的位置,一次排序后,该位置左边的数都比这个数小,右边的都比这个数大,这个数的位置不会再发生改变
2.1、设置两个变量 low、high,排序开始时:low=0,high=size-1。
2.2、整个数组找基准正确位置,所有元素比基准值小的摆放在基准前面,所有元素比基准值大的摆在基准的后面
默认数组的第一个数为基准数据,赋值给key,即key=array[low]。
因为默认数组的第一个数为基准,所以从后面开始向前搜索(high–),找到第一个小于key的array[high],就将 array[high] 赋给 array[low],即 array[low] = array[high]。(循环条件是 array[high] >= key;结束时 array[high] < key)
此时从前面开始向后搜索(low++),找到第一个大于key的array[low],就将 array[low] 赋给 array[high],即 array[high] = array[low]。(循环条件是 array[low] <= key;结束时 array[low] > key)
循环 2-3 步骤,直到 low=high,该位置就是基准位置。
把基准数据赋给当前位置。
2.3、第一趟找到的基准位置,作为下一趟的分界点。
2.4、递归调用(recursive)分界点前和分界点后的子数组排序,重复2.2、2.3、2.4的步骤。
2.5、最终就会得到排序好的数组。
# 54 26 93 17 77 31 44 55 20
# 根据定义,设置三个索引,low,high,以及当前排序的mid_value
# 先写核心操作代码:
# 一开始,mid_value是54,high指向20
# if alist[high] < mid_value: # 20<54
# alist[low] = alist[high] # 20应该在mid_value左边,直接把他扔给low(此时的alist[low]比mid_value大)
# low += 1
# elif alist[high] > mid_value:
# high -= 1
# if alist[low] < mid_value:
# low +=1
#
# elif alist[low] > mid_value:
# alist[high] = alist[low]
# high -= 1
def quick_sort(alist, first, last):
if first >= last: # 注意这里是大于等于,相当于只有first<last才执行下面的操作
return
mid_value = alist[first] # key=array[low]
low = first #
high = last #
while low < high:
# high 左移
while low < high and alist[high] >= mid_value: # ‘=’的目的是让同样的值在一边
high -= 1 # 如何防止索引超出,在每个while中加入终止条件high > low
alist[low] = alist[high] # 这里不用担心alist[low]丢失,因为mid_value = alist[low],这一次被移动的high放到了现在的low,对于第一次,也就是key的位置,该值不会再移动
while low < high and alist[low] < mid_value:
low += 1 # 如何防止索引超出,在每个while中加入终止条件high > low
alist[high] = alist[low] # 下一次low要被替换的值被保存到了high,该值不会再移动
# 从循环退出时,low==high
alist[low] = mid_value
# 对low左边的进行快排
quick_sort(alist, first, low - 1) # 为什么不能用切片,因为用了切片,每次的子任务相当于是对新的列表排序(切片实质是建立了一个新的列表),没有对alist排序,但是如果传入的是下标就可以了
# 对low右边的进行快排
quick_sort(alist, low + 1, last)
alist = [54, 26, 93, 17, 77, 31, 44, 55, 20]
print('排序前:',alist)
n = len(alist)
quick_sort(alist, 0, n-1)
print('排序后:',alist)
时间复杂度:每一级的时间复杂度是n,一共要分级,所以总的时间复杂度是
最坏时间复杂度:最坏
稳定性:不稳定
6.归并排序:
核心思路:保证子序列的是有序的,这样在合并子序列的时候,能保证遍正向遍历子序列的时候不会出现先遍历到大的值,再遍历到小的值
具体流程:
第一次拆分:对序列对半分
第二次拆分:再对拆分的序列分别对半分 ...
拆到每个子序列只有一个元素
然后相邻的子序列进行排序合并
对相邻的合并后的序列再进行排序时,用双指针
'''
第一次拆分:对序列对半分
第二次拆分:再对拆分的序列分别对半分
...
拆到每个子序列只有一个元素
然后相邻的子序列进行排序合并
对相邻的合并后的序列再进行排序时,用双指针
'''
def merge_sort(alist):
n = len(alist)
if n <= 1:
return alist
mid = n//2
# 归并排序不是在列表本身操作,所以可以用切片
# left是采用归并排序后形成的有序新的列表
left = merge_sort(alist[:mid])
right = merge_sort(alist[mid:])
# 将两个有序的子序列合并成新的整体
return merge(left, right)
def merge(left_li, right_li):
left_point, right_point = 0, 0
len_left = len(left_li)
len_right = len(right_li)
result = []
while right_point<len_right and left_point<len_left:
if left_li[left_point] <= right_li[right_point]:
result.append(left_li[left_point])
left_point += 1
else:
result.append(right_li[right_point])
right_point += 1
result += left_li[left_point:]
result += right_li[right_point:]
return result
alist = [54, 26, 93, 17, 77, 31, 44, 55, 20]
print('排序前:',alist)
n = len(alist)
a = merge_sort(alist)
print('排序后:',a)
所写代码的具体执行流程就是对原列表中相邻两个元素进行排序,然后将排序的结果与另外两个排序好的相邻的两个元素排序,这样就有四个排序好的元素,同样这四个排序好的元素再与另外四个(五个)排序好的元素排序...
时间复杂度:
切片拆分过程
合并过程:每一次合并的复杂度是,一共要合并次
所以总的时间复杂度是,最坏最优都是
稳定性:稳定,拆分的时候稳定,合并的时候注意先判断左边是不是小于等于右边,也要注意是小于等于,这样同样的值,也是左边的先进入序列,这样就是稳定的了
空间复杂度:需要创建一个新的列表