目录
排序
排序算法的稳定性
稳定排序算法会让原本有相等键值的纪录维持相对次序。也就是如果一个排序算法是稳定的,当有两个相等键值的纪录R和S,且在原本的列表中R出现在S之前,在排序过的列表中R也将会是在S之前。
冒泡排序
冒泡排序的过程
- 比较相邻的元素。如果第一个比第二个大(升序),就交换他们两个。
- 对每一对相邻元素作同样的工作,从开始第一对到结尾的最后一对。这步做完后,最后的元素会是最大的数。
- 针对所有的元素重复以上的步骤,除了最后一个。
- 持续每次对越来越少的元素重复上面的步骤,直到没有任何一对数字需要比较。
冒泡排序的实现
def bubble_sort(alist):
'''冒泡排序'''
n = len(alist)
for j in range(n-1): # ※注意! n个元素,最后一个元素是第n-1个,倒数第二个是第n-2个!
count = 0
for i in range(n-j-1): # ※两次循环的参数不要相同,用i和j
# 游标从头到尾
if alist[i] > alist[i+1]:
alist[i],alist[i+1] = alist[i+1],alist[i]
count += 1
if count == 0: # ※改进:如果某次走完一遍发现已经排好了,则跳出函数
return
冒泡排序的时间复杂度
- 最优时间复杂度:O(n) (表示遍历一次发现没有任何可以交换的元素,排序结束。)
- 最坏时间复杂度:O( n 2 n^2 n2)
- 稳定性:稳定
选择排序
选择排序的过程
- 在未排序序列中找到最小(大)元素,存放到排序序列的起始位置。
- 再从剩余未排序元素中继续寻找最小(大)元素,然后放到已排序序列的末尾。
- 以此类推,直到所有元素均排序完毕。
选择排序的实现
def select_sort(alist):
'''选择排序'''
for i in range(len(alist)-1): #0~n-2
min_index = i
for j in range(i+1, len(alist)):
if alist[min_index] > alist[j]:
min_index = j
alist[i],alist[min_index] = alist[min_index],alist[i]
选择排序的时间复杂度
- 最优时间复杂度:O( n 2 n^2 n2)
- 最坏时间复杂度:O( n 2 n^2 n2)
- 稳定性:不稳定(考虑升序每次选择最大的情况)
插入排序
插入排序的过程
选择排序过程1:
- 在未排序序列中找到最小(大)元素,存放到排序序列的起始位置。
- 再从剩余未排序元素中继续寻找最小(大)元素,然后放到已排序序列的末尾。
- 以此类推,直到所有元素均排序完毕。
选择排序过程2(后续实现的): - 左侧开始第一个元素为有序序列
- 将剩余未排序元素中的第一个插入到左侧的有序序列
- 以此类推,直到所有元素均排序完毕
插入排序的实现
def insert_sort(alist):
'''插入排序'''
n = len(alist)
for i in range(1, n):
a1 = i # a1所指的就是将要插入到前面有序序列的值
for j in range(i-1, -1, -1): # 从有序序列的末尾向前比较,如果要插入的值更小,就交换,直到到达合适的位置
if alist[a1] < alist[j]:
alist[a1], alist[j] = alist[j], alist[a1]
a1 = j
else: # 优化:如果不比j指针对应的值小了,就直接退出循环
break
插入排序的时间复杂度
- 最优时间复杂度:O(n) (升序排列,序列已经处于升序状态)
- 最坏时间复杂度:O( n 2 n^2 n2)
- 稳定性:稳定
希尔排序
希尔排序(Shell Sort)是插入排序的一种,也称缩小增量排序,是直接插入排序算法的一种更高效的改进版本。
希尔排序的过程
- 按一定步长(间隔)进行分组
- 对每组元素分别进行插入排序
- 缩小步长(间隔)
- 重复上述过程,直到步长为1时的那一组插入排序完毕
希尔排序的实现
def shell_sort(alist):
'''希尔排序'''
n = len(alist)
gap = n // 2
while gap != 0:
for i in range(gap,n):
a1 = i
j = a1 - gap
while j >= 0:
if alist[a1] < alist[j]:
alist[a1], alist[j] = alist[j], alist[a1]
a1 = j
j -= gap
else:
break
gap = gap // 2
希尔排序的时间复杂度
- 最优时间复杂度:根据步长序列的不同而不同
- 最坏时间复杂度:O( n 2 n^2 n2)
- 稳定性:不稳定
快速排序 ※
快速排序的过程
- 从数列中挑出一个元素,称为"基准"(pivot),
- 重新排序数列,所有元素比基准值小的摆放在基准前面,所有元素比基准值大的摆在基准的后面(相同的数可以到任一边)。在这个分区结束之后,该基准就处于数列的中间位置。这个称为分区(partition)操作。
- 递归地(recursive)把小于基准值元素的子数列和大于基准值元素的子数列排序。
快速排序的实现
def quick_sort(alist, first, last):
'''快速排序'''
if first >= last:
return
mid_value = alist[first]
low = first
high = last
while low < high:
while low < high and alist[high] >= mid_value: # 让high游标左移:只有两个指针不重合并且所指元素符合大小要求,指针才会继续进行
high -= 1
alist[low] = alist[high] # 把high指的元素放到low指的位置上
while low < high and alist[low] < mid_value: # 让low游标右移:# 只有两个指针不重合并且所指元素符合大小要求,指针才会继续进行
low += 1
alist[high] = alist[low]
# 退出循环时,low == high
alist[high] = mid_value # 再把作比较的值放到中间位置
# 递归
# 对low左边的列表执行快速排序
quick_sort(alist,first,low-1)
# 对low右边的列表执行快速排序
quick_sort(alist,low+1,last)
快速排序的时间复杂度
- 最优时间复杂度:O( n l o g 2 n nlog_2n nlog2n)
- 最坏时间复杂度:O( n 2 n^2 n2)
- 稳定性:不稳定
归并排序
归并排序是采用分治法的一个非常典型的应用。归并排序的思想就是先递归分解数组,再合并数组。
归并排序的过程
- 将数组分解最小,
- 然后合并两个有序数组:基本思路是比较两个数组的最前面的数,谁小就先取谁,取了后相应的指针就往后移一位。然后再比较,直至一个数组为空,最后把另一个数组的剩余部分复制过来即可。
- 以此类推,合并到最后成一个数组即可
归并排序的实现
递归的过程一定要自己画图走一遍!
def merge_sort(alist):
'''归并排序'''
'''使用递归'''
n = len(alist)
if n <= 1:
return alist
mid = n // 2
# left_li 表示采用归并排序后形成的有序的新的列表
left_li = merge_sort(alist[:mid])
# right_li 表示采用归并排序后形成的有序的新的列表
right_li = merge_sort(alist[mid:])
# 将两个有序的子序列合并为一个新的整体
left_pointer, right_pointer = 0, 0
result = []
while left_pointer < len(left_li) and right_pointer < len(right_li):
if left_li[left_pointer] <= right_li[right_pointer]:
result.append(left_li[left_pointer])
left_pointer += 1
else:
result.append(right_li[right_pointer])
right_pointer += 1
result += left_li[left_pointer :]
result += right_li[right_pointer :]
return result
归并排序的时间复杂度
- 最优时间复杂度:O( n l o g 2 n nlog_2n nlog2n)
- 最坏时间复杂度:O( n l o g 2 n nlog_2n nlog2n)
- 稳定性:稳定
搜索(查找)
搜索是在一个项目集合中找到一个特定项目的算法过程。搜索通常的答案是真的或假的。 搜索的几种常见方法:顺序查找、二分法查找、二叉树查找、哈希查找
二分查找
二分查找又称折半查找,优点是比较次数少,查找速度快,平均性能好;其缺点是要求待查表为有序表,且插入删除困难。
二分查找的过程
- 假设表中元素是按升序排列,将表中间位置记录的关键字与查找关键字比较,如果两者相等,则查找成功。
- 否则利用中间位置记录将表分成前、后两个子表,
- 如果中间位置记录的关键字大于查找关键字,则进一步查找前一子表,否则进一步查找后一子表。
- 重复以上过程,直到找到满足条件的记录,使查找成功,或直到子表不存在为止,此时查找不成功。
二分查找的实现
有递归版和非递归版两种代码。
def binary_search_1(alist,item):
'''二分查找'''
'''递归'''
n = len(alist)
if n > 0: # 递归循环的条件
mid = n // 2
if alist[mid] == item:
return True
elif item < alist[mid]:
return binary_search(alist[:mid], item) # ※注意要有返回值
else:
return binary_search(alist[mid+1:], item) # ※注意要有返回值
return False
def binary_search_2(alist,item):
'''二分查找'''
'''非递归'''
n = len(alist)
first = 0
last = n-1
while first <= last:
mid = (first + last) // 2
if alist[mid] == item:
return True
elif item < alist[mid]:
last = mid - 1
else:
first = mid + 1
return False
二分查找的时间复杂度
- 最优时间复杂度:O(1)
- 最坏时间复杂度:O( l o g 2 n log_2n log2n)