学习黑马程序员算法笔记
冒泡排序
冒泡排序是最基础的交换排序,会遍历序列n-1次,每一次遍历序列时都会挑选出剩余序列中的最大值,
冒泡排序的大概流程是:使用两个for循环,第一层for循环控制的是遍历的次数,设置成range(1,length),第二层for循环控制的是遍历序列长度。每一次遍历都会有一个最大的数排序好(从结尾想开头排序),所以每次遍历的时候在第二层控制遍历的序列的长度,减少时间复杂度,提高效率。
第一层for循环控制的是遍历的次数,
第二层for循环控制的是遍历的序列的长度,遍历次数count,序列长度是[0,length-count)
每次遍历选择最大值,一次从结尾向前从大到小排序
eg:原始数据:[16, 8, 9,25,5]
第一次遍历:count=1
遍历的序列长度:[0, 5-count),length=5(注:最后一个索引是3,会将索引4的数据读出。)
[8, 9, 16, 5, 25]
第二次遍历:count=2
遍历的序列的长度:[0,5-count),
[8, 9, 5, 16, 25 ] (注:25表示不参与排序,就是length-count实现控制参与排序的序列的长度,因为经过一次排序之后,参与排序的序列最后一个值对应的就是序列中的最大值 )
第三次遍历:count=3
遍历的序列的长度:[0,5-count),
[8, 5, 9, 16, 25 ]
第四次遍历:count=4
遍历的序列的长度:[0,5-count),
[5, 8, 9, 16, 25 ]
结果:[5, 8, 9, 16, 25]
def bubble_sort(sequence: list):
"""
冒泡排序
每一次遍历将最大值放在最后,每一次遍历查找都是将找出最大数值,一个列表中,会遍历n-1次,
:param sequence:
:return:
"""
length = len(sequence)
if length < 2: # length=0:sequence是一个空的;length=1:只有一个数值,不用排序
return
for count in range(1, length): # count代表的是第几遍遍历sequence,需要遍历length-1次之后才会完成所有的元素的对比
# 每次循环结束之后,sequence的最后一个元素是最大值
change = False # 序列有没有改变过顺序,False表示没有改表过顺序,True表示顺序改变过
for index in range(0, length - count): # 对sequence进行一次排序
if sequence[index] > sequence[index + 1]: # 前一个元素大于后一个元素
# 二者交换位置
sequence[index], sequence[index + 1] = sequence[index + 1], sequence[index]
change = True #
if not change:
break
选择排序
遍历n-1次,每次遍历选出最小值对应的索引,遍历结束之后,进行数据交换,
交换的数据是第count次遍历就是索引是count对应的元素与最小值对应的索引出的值交换
# =========================================================================#
# 选择排序,每次循环都是选择序列中的最小值,
# 每次选择的是最小值的时候,排序算法是稳定的;
# 每次选择的是最大值的时候,排序算法是不稳定的
# =========================================================================#
def select_sort(sequence):
"""
选择排序
原理:第一次遍历找到所索引范围0-length-1的最小值放在索引0,第二次找到索引范围1-length-1的最小值放在索引1处,,,
通过list[count], list[min_value_index] = list[min_value_index], list[count]交换数据
:param sequence:
:return:
"""
length = len(sequence)
if length < 2:
return
# 操作索引更方便
# 使用count控制遍历此时,每一次新的遍历开始,寻找最小值的序列都会少一个长度,n-1次
for count in range(1, length): # count:1, 2, 3, ... , length - 1
start_index = count - 1 # 开始的索引的位置
min_value_index = start_index # 用来记录最小值对应的索引,每一次遍历,count都会比上一次+1
change = False# 需要不需要交换至
for index in range(count, length): # 遍历所有值,
"""
每次循环,都会是将最小值移动到最左侧,
下一次遍历时会将已经排序好的部分隔离出去,只遍历没有排序的部分
"""
if sequence[index] < sequence[min_value_index]: # 找最小值对应的下标
min_value_index = index
change = True
# 将最小值放在剩余序列的开头
if change: # 如
sequence[start_index], sequence[min_value_index] = sequence[min_value_index], sequence[start_index]
插入排序
从索引1开始遍历一直到索引n-1,第count次遍历就是以index=count为界左边是有序部分,右边是无序部分,
通过遍历对比index=count和有序部分的值,找到index对应的位置,
# ======================================================================================#
# 插入算法,是一种简单直观的排序算法,
# 工作原理是通过构建有序序列,对于未排序的数据,在已排序序列中从后向前扫描,找到相应的位置并插入。
# 插入排序在实现上,在从后向前扫描过程中,需要反复把已排序的元素逐步向后挪位,为新的元素提供插入空间。
# 最优时间复杂度:O(n)
# 最坏时间复杂度O(n^2)
# 稳定性:稳定
# ======================================================================================#
def insertion_sort(sequence: list):
length = len(sequence) # 序列长度
# 从右边的无需序列中取出第几个元素执行线面的过程
# 划分:左边是有序序列,右边是无序序列,每次遍历都是从右边取出一个元素,通过在左边做对比,将元素放在对应的位置上
for count in range(1, length): # count代表的是遍历的次数,取值范围1~n-1,也是每次遍历要将比较的数值的索引
# 对第count个元素进行对比插入到左边有序的序列中
# 下面遍历作用是将第count个元素在左边有序部分找到对应的位置
for index in range(count, 0, -1): # 将索引为count的数值和count, ..., 1的数值全部比对,
if sequence[index] < sequence[index - 1]: # 如果满足数值前移的条件,就将index-1对应的数值移动到index
sequence[index - 1], sequence[index] = sequence[index], sequence[index - 1] # 交换数据
else: # 不需要交换的意思就是:原序列的第count个元素在左边有序部分找到了它的对应的位置
break
希尔排序
希尔排序是改进版的插入算法,通过增量来控制序列分组,最终达到排序效果
# =============================================================================================#
# 希尔排序:插入排序的一种,也称缩小增量排序,是直接排序算法的一中更高效的改进版本。
# 希尔排序是非稳定的排序算法,希尔排序是把记录按下标的一定增量分组,对每组使用直接插入排序算法排序;
# 随着增量的减少,每组包含的关键词越来越多,当增量减至1时,整个文件恰被分成一组,算法终止。
# gap的取值是需要数学计算的,通过计算才能确定最高效的gap的取值
# 稳定性:不稳定
# =============================================================================================#
def shell_sort(sequence: list):
length = len(sequence)
if length < 2: # 空列表或者只有一个元素的列表是不需要排序的
return
gap = length // 2
while gap > 0: # 控制gap的步长
# 插入排序
for count in range(gap, length):
for index in range(count, 0, -gap):
if sequence[index] < sequence[index - gap]: # 前一个比后一个的值大
sequence[index - gap], sequence[index] = sequence[index], sequence[index - gap]
else:
break
gap = gap // 2 # 缩短gap步长
快速排序(必须掌握的排序算法)
从序列中挑选出一个元素作为“基准”,通过遍历最终得到将左边序列是小于等于基准的元素,右边序列是大于基准的元素,
通过递归算法,分别对左边子序列和右边子序列进行快速排序,
# ===================================================================================================================#
# 快速排序,又称划分交换排序,通过一趟排序将要排序的数据分割成独立的两部分,
# 其中一部分的所有数据都比另外一部分的所有数据都要小,然后再按此方法对两部分数据分别进行快速排序,
# 整个排序过程可以递归进行,以此达到整个数据换成有序序列。
# 步骤为 1.从数列中挑选出一个元素,称为基准;
# 2.重新排序数列,所有元素比基准值小的摆放在基准前面,所有元素比基准值大的摆在基准后面(相同的数可以放在任一边),
# 在这个分区结束之后,该基准就处于数列的中间位置。这个称为分区操作;
# 3.递归把小于基准值元素的子数列和大于基准值元素的子数列排序
# 最优时间复杂度:O(n*log(n))
# 最坏时间复杂度:O(n^2)
# 稳定性:不稳定
# ===================================================================================================================#
def quick_sort(sequence: list, first, last):
# 只有一个元素,low=0时,不满足条件时退出执行,相当于对序列不进行任何操作,
# 当first=last时说明基准左侧只有一个元素,无需排序,
# first=last+1时,说明基准的位置没有改变,没有元素排序
if first >= last:
return
mid_value = sequence[first] # 挑选基准,将基准挑选出来之后,相当于在序列中空余出一个位置,可以对对应位置进行赋值,不用数值交换
low = first # 值比mid_value小的值对应的索引
high = last # 值比mid_value大的值对应的索引
# 寻找基准的位置索引,并将基准两侧按照左侧全是小于等于基准的,右侧全是大于基准的原则对序列进行排列
# 每交换一次数值之后,low和high交替改变,交替条件是:有数值的交换
while low < high: # 循环结束之后,low = high
# high左移
while low < high and sequence[high] >= mid_value: # 等号位于哪相等的元素放在哪边
high -= 1
# 跳出循环的有两个条件:low=high,说明找到了基准的位置;sequence[high]<mid_value(基准),需要交换值
sequence[low] = sequence[high] # sequence[high]<mid_value,交换元素
# low右移
while low < high and sequence[low] < mid_value:
low += 1
sequence[high] = sequence[low]
# 循环结束之后,low=high,low和high指向的索引位置就是基准所在的位置
sequence[low] = mid_value
# 对基准左侧的子序列进行排序,当first=low时,说明基准是序列的最小值
quick_sort(sequence, first, low - 1)
# 对基准的右侧序列进行排序,当low=last时说明基准是序列中的最大值
quick_sort(sequence, low + 1, last)
归并排序
归并排序首先是对半将序列分开,之后分别对两边的序列进行排序,使用递归实现
# ======================================================================================================#
# 归并排序:使用递归
# 归并排序是采用分治法的一个典型应用。归并排序的思想就是先递归分解数组,在合并数组。
# 将数组分解最小之后,人后合并两个有序数组,基本思想是比较两个数组的最前面的数,谁小就先取谁,
# 取了后相应的指针就往后移一位。人后再比较,直至一个数组为空,最后把另一个数组的剩余部分复制过来即可。
# 最优时间复杂度:O(n*log(n))
# 最坏时间复杂度:O(n*log(n))
# 稳定性:稳定
# 做为对比的话:归并排序时间复杂度最小,但是空间复杂度是最大的,因为是需要一个新的列表来保存输出,需要新的内存空间
# ======================================================================================================#
def merge_sort(sequence: list) -> list:
# 拆分过程,将一个序列对半拆分,奇数右边比左边多
length = len(sequence)
# 退出递归的条件,只有一个元素的序列是不需要排序的
if length <= 1:
return sequence
"""以下部分不一定能够执行到,当只有一个元素时,就没有必要拆分了,就会直接将列表返回"""
mid = length // 2 # 拆分的中点
# left 采用归并排序之后形成的有序的新的列表
left_sequence = merge_sort(sequence[:mid]) # 输入是对列表的切片,所以输入是一个列表
length_left = len(left_sequence) # 长度
# right 采用归并排序之后形成的有序的新的列表
right_sequence = merge_sort(sequence[mid:]) # 输入是对列表的切片,所以输入是一个列表
length_right = len(right_sequence) # 长度
# 合并元素merge(left, right),将两个有序的子序列合并成一个整体
left_pointer = 0 # 左边子序列的指针
right_pointer = 0 # 右边子序列的指针
result = [] # 保存结果的列表
# 循环控制合并元素,任何一个指针到头就会跳出循环,跳出循环时,另一个子序列还会有剩余元素,将剩余的元素直接加到result之后
while left_pointer < length_left and right_pointer < length_right: # 指针不能超过列表长度范围
# 遍历对比两个子序列的值,小的先放在result列表中
if left_sequence[left_pointer] <= right_sequence[right_pointer]: # 感觉等于号放在这,算法是稳定的
result.append(left_sequence[left_pointer])
left_pointer += 1
else:
result.append(right_sequence[right_pointer])
right_pointer += 1
# 将剩余部分的元素添加进result之后
result += left_sequence[left_pointer:]
result += right_sequence[right_pointer:]
return result
二分查找
# ========================================================================================================#
# 二分查找:操作对象必须是有序的,使用的对象只能是顺序表
# ========================================================================================================#
def binary_search1(sequence: list, item):
"""
递归方法
:param sequence:
:param item:
:return: bool:True:找到了,False:没有找到
"""
length = len(sequence)
# 退出递归的条件
if length > 0:
mid = length // 2
if sequence[mid] == item:
return True
elif item < sequence[mid]: # 左边
return binary_search1(sequence[:mid], item)
else: # 右边
return binary_search1(sequence[mid + 1:], item)
else: # length = 0,说明没有在这个序列中查找到item
return False
def binary_search2(sequence, item):
"""
非递归方法
:param sequence:
:param item:
:return:
"""
length = len(sequence)
first = 0
last = length - 1
# 跳出循环时,有两种情况:找到了返回的是True;没有找到
while first <= last:
mid = (first + last) // 2
if sequence[mid] == item:
return True
elif item < sequence[mid]: # 左边
last = mid - 1
else: # 右边
first = mid + 1
return False # 执行这一句时,说明没有找到item