前言: 刚学完排序,快期末考试了,总结一下吧,加深一下印象,如果有不正确的地方请大家不吝赐教, 也可以和大家讨论一下各种算法的优化。
各种排序算法就不仔细的分类了。
注: 算法都是基于生成升序序列。
插入排序(普通插入排序及二分版)
原理:将序列分为有序序列和无序序列两部分,初始时有序序列为一(一个元素就是有序的),然后取无序序列第一个值,在有序序列中找出属于他的位置,将该值插入该位置即可,重复若干次。
复杂度:O(n^2)
1、普通插入排序:
def InsertSort( lst ):
for i in range(1, len(lst)):
while i > 0 and lst[i] < lst[i-1]: # 循环判断属于lst[i]的位置,并交换
lst[i], lst[i-1] = lst[i-1], lst[i]
i -= 1
2、二分查找插入排序:
二分查找的目标位置:寻找刚好小于等于lst[position]的值右边的第一个位置
(与普通插入排序相比,二分查找插入排序并没有很实质性的改进,仅仅引入了二分查找)
def BinarySearch(lst, left, right, position): # 寻找无序序列第一个元素在有序序列中的对应位置
while left <= right: # 二分查找目标位置
# 左右边界相同但仍要进行判断,如有序序列长度为一时
mid = (left + right)//2
if lst[mid] <= lst[position]: # 寻找刚好小于等于lst[position]的值右边的第一个位置
left = mid + 1
else:
right = mid - 1
'''mid-1的作用:防止陷入死循环,如有序序列长度为1时,lst[0] = 1, 无序序列第一个元素lst[1] = 0时,
如果不mid-1, 就会陷入死循环。
'''
return left # left的位置就是目标位置
def BinarySearchInsertSort(lst): # 二分查找插入排序
for i in range(1, len(lst)):
pos = BinarySearch(lst, 0, i-1, i)
for j in range(i, pos, -1): # 将无序部分第一个元素移动到有序部分对应位置
lst[j], lst[j-1] = lst[j-1], lst[j]
冒泡排序(逐步优化)
原理:像冒泡一样从头到尾,一遍又一遍的将逆序的交换顺序,重复若干次。
复杂度:O(n^2)
def BubbleSort(lst): # 普通冒泡
for i in range(len(lst)):
for j in range(0, len(lst)-1):
if lst[j] > lst[j+1]:
lst[j], lst[j+1] = lst[j+1], lst[j]
def BubbleSort1(lst): # 记录有序部分
for i in range(len(lst)-1, -1, -1): # 每一轮冒泡都能至少能排好一个位置
for j in range(0, i): # 只遍历无序部分
if lst[j] > lst[j+1]:
lst[j], lst[j+1] = lst[j+1], lst[j]
def BubbleSort2(lst): # 增加逆序因子, 判断一趟排序是否存在逆序,若不存在,则序列已有序
for i in range(len(lst)-1, -1,-1):
flag = 1 # 逆序因子
for j in range(0, i):
if lst[j] > lst[j+1]:
flag = 0
lst[j], lst[j+1] = lst[j+1], lst[j]
if flag: # 不存在逆序对,跳出循环
break
选择排序
原理:将整个序列分为有序序列的无序序列,初始时有序序列长度为0,然后通过遍历无序序列,找到最小值,然后与无序序列第一个元素交换,更新有序序列,重复若干次。
复杂度:O(n^2)
def SelectSort(lst):
for i in range(len(lst)-1): # i是有序序列的长度,且当有序序列的末端位置为len(lst)-2时,最后一个元素就是有序的,所以遍历只需len(lst)-1次
minn = float('inf') # 初始时将最小值设置成无穷大
pos = i # 记录最小值的位置
for j in range(i, len(lst)): # 遍历无序序列
if minn > lst[j]: # 每轮遍历都选择无序序列最小值
minn = lst[j]
pos = j
lst[i], lst[pos] = lst[pos], lst[i] # 交换位置
快速排序
原理:通过选择一个轴心元素,将序列中的元素小与轴心元素的放在轴心元素左边,大于则放在右边。然后以轴心元素最终位置将序列分成两个部分,重复若干次,直到子序列为一个元素。
复杂度:平均复杂度:O(nlogn) 最坏复杂度:O(n^2)
def QuickSort(lst, left, right):
if right <= left: # 完成标志
return
i = left
j = right
pivot = lst[i] # 选择基准值
'''对于基准值的选取,最好选择左边界的元素,因为基准值的位置是由i或j来代表的,
后面将元素交换在基准值时是将基准值元素位置看为空位置,交换元素时是将元素覆盖基准值位置,
然后基准值位置变为交换位置的位置,左右重复操作,直到i>=j;
如果选择中间元素基准值的位置就无法由i或j来代表,算法就不成立了
如果要选择中间的元素,则需要交换到左边界,即可保证算法的成立
注:选左边界为基准值时,需从右边界开始判断
选取右边界为基准值时,需从左边界开始判断
原因:认为基准值位置为空位置,需从空位值的另一边开始找目标元素,找到后交换位置
再从空位置的另一侧寻找元素,如果从空位置侧开始,会丢失对空位置的标记,也不符合这种算法的原理'''
while i < j: # 交换元素位置,基准值左边都小于基准值,右边都大于基准值
while i < j and pivot < lst[j]:
j -= 1
if i < j:
lst[i] = lst[j]
i += 1
while i < j and lst[i] < pivot:
i += 1
if i < j:
lst[j] = lst[i]
j -= 1
lst[i] = pivot
QuickSort(lst, left, i-1) # 递归进入左右序列
QuickSort(lst, i+1, right)
归并排序
原理:首先先对无序序列进行不断的二分,直到每个部分只有一个元素,然后对序列进行两两合并,合并时对序列进行排序,这样不断合并最总将获得有序序列。
代码分为两部分,Merge为对序列的合并,MergeSort为主函数,利用递归对序列进行划分。
复杂度:O(nlogn)
def Merge(left, right): # 对序列进行合并
l, r = 0, 0
result = []
while l < len(left) and r < len(right): # 逐个比较加入result
if left[l] < right[r]:
result.append(left[l])
l += 1
else:
result.append(right[r])
r += 1
result += left[l:] # 将剩下长度的序列加入result
result += right[r:]
return result
def MergeSort(lst):
if len(lst) <= 1:
return lst
mid = len(lst)//2
left = MergeSort(lst[:mid]) # 利用递归和切片对序列进行分割
right = MergeSort(lst[mid:])
return Merge(left, right)
其他排序(Shell插入排序……)
Shell插入排序:取分组因子,把序列分成k组,对k组内部进行插入排序,分组因子逐渐变小直到一。
复杂度:平均复杂度:O(nlogn)
最坏复杂度:O(n^2)
'''取分组因子,把序列分成k组,对k组内部进行插入排序,分组因子逐渐变小直到一'''
def ShellSort(lst):
k = (len(lst))//2 # 不需要(len(lst)+1)//2,这样会多出一个单元素
while k: # k为分组因子
for i in range(k+1): # 对k组分别进行插入排序
for j in range(i+k, len(lst), k): # 插入排序
while j > i and lst[j] < lst[j-k]:
lst[j], lst[j-k] = lst[j-k], lst[j]
j -= k
k = k//2