ダークホース プログラマ アルゴリズムの学習ノート
バブルソート
バブル ソートは最も基本的な交換ソートです。シーケンスを n-1 回走査し、シーケンスを走査するたびに、残りのシーケンスの最大値を取り出します。バブル ソートの一般的なプロセスは次のとおりです
。ループ、最初の層 for ループは、range(1, length) に設定されるトラバーサルの数を制御し、for ループの 2 番目の層はトラバーサル シーケンスの長さを制御します。各走査では最大の数がソートされる (終わりから始めにソートされる) ため、各走査で 2 番目の層で走査されるシーケンスの長さが制御され、時間の複雑さが軽減され、効率が向上します。
for ループの最初の層は走査数を制御し、
for ループの 2 番目の層は走査されるシーケンスの長さ、走査数カウントを制御します。シーケンスの長さは [0, length-count) です。各走査の最大値を選択します
。 、最後から 1 回 大きい順から小さい順にソートします
。 例: 元のデータ: [16, 8, 9, 25, 5]
最初の走査: count=1
走査されたシーケンスの長さ: [0, 5-count), length=5 (注) : 最後のインデックスは 3 で、インデックス 4 のデータが読み出されます。)
[8, 9, 16, 5, 25]
2 回目のトラバース: count=2
トラバースされたシーケンスの長さ: [0, 5-count) ,
[ 8, 9, 5, 16, 25 ] (注: 25はソートに参加しないことを意味します。つまり、length-count はソートに参加するシーケンスの長さの制御を実現します。ソート後は、ソートに参加するシーケンスはシーケンスに対応します (最大値)
3 回目のトラバース: count=3
トラバースされたシーケンスの長さ: [0, 5-count),
[8, 5, 9, 16, 25 ]
4 番目のトラバーサル: 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 に対応する要素と、最小値。
# =========================================================================#
# 选择排序,每次循环都是选择序列中的最小值,
# 每次选择的是最小值的时候,排序算法是稳定的;
# 每次选择的是最大值的时候,排序算法是不稳定的
# =========================================================================#
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 と の値を走査して比較することで
、順序付けられた部分、対応する位置のインデックスを見つけます、
# ======================================================================================#
# 插入算法,是一种简单直观的排序算法,
# 工作原理是通过构建有序序列,对于未排序的数据,在已排序序列中从后向前扫描,找到相应的位置并插入。
# 插入排序在实现上,在从后向前扫描过程中,需要反复把已排序的元素逐步向后挪位,为新的元素提供插入空间。
# 最优时间复杂度: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