一,冒泡排序
def bubble_sort(arr):
"""冒泡排序"""
# 第一层for表示循环的遍数
for i in range(len(arr) - 1):
# 第二层for表示具体比较哪两个元素
for j in range(len(arr) - 1 - i):
if arr[j] > arr[j + 1]:
# 如果前面的大于后面的,则交换这两个元素的位置
arr[j], arr[j + 1] = arr[j + 1], arr[j]
return arr
"""
冒泡排序就和水中的泡泡一样,越大的数冒的越快,从冒泡排序算法的逻辑来看,就是一个先大再小的排序
因为是两两比较,当前的数和后面的一个数做大小的比较,所以数组中有n个数就比较n-1轮,
在每轮的比较中,如果当前的数比后面的一个数大,就交换位置,以这样的方法,最大的数就会被最先
比较出来,放在最后,以此类推,每轮过后,都最起码有一个数字会被比较出来,加入数组后面的排好序的
队伍当中,于是第二轮的比较只需要n-1-i次的比较即可
冒泡排序是比较排序的一种
稳定性好,但是效率比较差,因为每轮循环比较都可能需要多次替换元素的位置,而元素之间的
位置替换是要消耗计算机资源的,仅适合数据少的时候使用
时间复杂度为O(n^2)
空间复杂度为O(1)
"""
二,选择排序
def selection_sort(arr):
"""选择排序"""
# 第一层for表示循环选择的遍数
for i in range(len(arr) - 1):
# 将起始元素设为最小元素
min_index = i
# 第二层for表示最小元素和后面的元素逐个比较
for j in range(i + 1, len(arr)):
if arr[j] < arr[min_index]:
# 如果当前元素比最小元素小,则把当前元素角标记为最小元素角标
min_index = j
# 查找一遍后将最小元素与起始元素互换
arr[min_index], arr[i] = arr[i], arr[min_index]
return arr
"""
选择排序和冒泡排序的逻辑是正好相反的,冒泡排序是先选大的,而选择排序是先选小的
因为也是两个数之间的比较,如果数组内有n个数,外层循环只需要循环n-1就好了,
内层循环则是执行比较以及保存最小数位置的业务逻辑的,因为每轮循环结束,都会从小到大排好
一个数的位置,所以内层只需要循环i+1到n次就行了
先假定第一个数就是最小的数,然后依次向后比较,如果发现有比我们假定的还小的数,就用一个
变量保存它的索引位置,并不会立刻交换两个数的位置,直到将后面的数完全比较完,确定我们
变量保存的索引相对应的数是最小的数,才会去交换二者位置。
后面以此类推
选择排序每次比较大小后因为不会立刻去交换二者位置,每轮只有一次甚至没有交换发生,所以
较冒泡排序消耗cpu资源较少,但也仅适合数据量较少的时候使用
选择排序为比较排序
因为存在任意位置的两个元素交换,比如[5, 8, 5, 2],第一个5会和2交换位置,所以
改变了两个5原来的相对顺序,所以为不稳定排序
选择排序同样是双层循环n*(n-1)),所以时间复杂度也为:O(n^2)
需要常数个辅助单元,所以空间复杂度也为O(1)
"""
三,插入排序
def insertion_sort(arr):
"""插入排序"""
# 第一层for表示循环插入的遍数
for i in range(1, len(arr)):
# 设置当前需要插入的元素
current = arr[i]
# 与当前元素比较的比较元素
pre_index = i - 1
while pre_index >= 0 and arr[pre_index] > current:
# 当比较元素大于当前元素则把比较元素后移
arr[pre_index + 1] = arr[pre_index]
# 往前选择下一个比较元素
pre_index -= 1
# 当比较元素小于当前元素,则将当前元素插入在 其后面
arr[pre_index + 1] = current
return arr
"""
插入排序的逻辑就是,从数组中第二个元素开始,用当前元素与前面的每一个数据作比较,如果
当前元素小于前面一个元素,就与之交换,然后再继续与前面一个元素做比较,直到当前元素
大于或等于前面一个元素为止
这样每轮过后,数组中都会增加一个数被按照从小到大的次序排序好,直到循环结束
插入排序也是比较排序
具有稳定性
因为也需要循环比较
时间复杂度为O(n^2)
空间复杂度为O(5)
"""
四,希尔排序
def shell_sort(arr):
"""希尔排序"""
# 取整计算增量(间隔)值
gap = len(arr) // 2 #2
while gap > 0:
# 从增量值开始遍历比较
for i in range(gap, len(arr)): # 2 5
j = i #2
current = arr[i] #1
# 元素与他同列的前面的每个元素比较,如果比前面的小则互换
while j - gap >= 0 and current < arr[j - gap]: #0
arr[j] = arr[j - gap]
j -= gap
arr[j] = current
# 缩小增量(间隔)值
gap //= 2
return arr
"""
希尔排序将数组分成line = n//2列,对每列进行排序,排好序后,再分为line = line//2列再进行排序,
直到line = 1时,使用插入排序算法,即完成了数组排序
排序时元素之间需要比较,所以为比较排序
因为希尔排序是间隔的插入,所以存在相同元素相对顺序被打乱,所以是不稳定排序
最坏时间复杂度O(n^2)平均复杂度为O(n^1.3)
只需要常数个辅助单元,所以空间复杂度也为O(1)
插入排序是每轮都是一小步,希尔排序是先大步后小步,它第一个突破O(n2)的排序算法。
"""
l1 = ['2','3','1','0','-1']
l1 = shell_sort(l1)
print(l1)
五,归并排序
def merge_sort(arr):
"""归并排序"""
if len(arr) == 1:
return arr
# 使用二分法将数列分两个
mid = len(arr) // 2
left = arr[:mid]
right = arr[mid:]
# 使用递归运算
return marge(merge_sort(left), merge_sort(right))
def marge(left, right):
"""排序合并两个数列"""
result = []
# 两个数列都有值
while len(left) > 0 and len(right) > 0:
# 左右两个数列第一个最小放前面
if left[0] <= right[0]:
result.append(left.pop(0))
else:
result.append(right.pop(0))
# 只有一个数列中还有值,直接添加
result += left
result += right
return result
"""
归并排序用到了递归,将数组进行二分,分到不可再分为止,也就是将数组拆分为一个个长度为1的
数组,到了这一步就会触发终止递归的条件,然后return每一个被拆分的数组,调用merge进行逐成合并
排序,终止合并为一个被排好序的原数组
排序时元素之间需要比较,所以为比较排序
我们从代码中可以看到当左边的元素小于等于右边的元素就把左边的排前面,而原本左边的就是在前面,
所以相同元素的相对顺序不变,故为稳定排序
时间复杂度: 复杂度为O(nlog^n)
空间复杂度:在合并子列时需要申请临时空间,而且空间大小随数列的大小而变化,所以空间复杂度为O(n)
"""