文章目录
一. 排序算法
1. 定义
假设含有n 个记录的序列{r1 , r2 ,r3, …,rn},其相对应的关键字分别为:{k1 , k2, …, kn},需要确定1,2,3,4…n 的一种排列 p1,p2 ,p3…pn其相对应的关键字满足kp1<= kp2 <= kp3…<=kpn(非递减或者非递增)关系,即使得序列称为一个按关键字有序的序列{rp1,rp2…rpn} ,这样的操作称为排序。对一序列对象根据某个关键字进行排序。多个序列的排序最终都可以转换成单个关键字的排序。
2. 稳定与不稳定
稳定:如果a 原本在b 的前面,而a=b ,排序之后a仍然在b的前面
不稳定: 如果a 原本在b的前面,而a = b ,排序之后a可能出现在b的后面。
3. 内排序和外排序
内排序: 所有的排序操作都是在内存中完成的
外排序: 由于数据太大,因此 把数据放在磁盘中,而排序通过磁盘和内存的数据传输才能进行。
4. 排序算法的性能影响因素
1) 时间性能(时间复杂度) : 一个算法执行所消耗的时间
2) 辅助空间 (空间复杂度): 运行完一个程序所需要的内存大小
3) 算法的复杂性: 算法本身的复杂性,而不是指时间的复杂性
(ps:各种排序算法的时间复杂性总结:
5. 排序算法的分类
二. 冒泡排序
1. 定义
冒泡排序是一种交换排序 ,基本思想是: 两两比较相邻记录的关键字,如果反序则交换,直到没有反序的记录为止。 这个算法的名字来源是因为越大地的元素回经由交换慢慢到数列的顶端。
2. 冒泡算法的步骤
1)比较相邻的元素。如果第一个比第二个大,那么就交换第一个和第二个
2) 对每一对相邻元素做同样的工作,从第一对到结尾的最后一对,这一步做完之后,最后的元素将是最大的数
3) 针对所有元素重复上面的操作,除了最后一个
4) 持续每次对来越少的元素重复上面的步骤,直到没有任何一对数字需要比较
eg :
当遇到序列中有相同的元素的时候:
原始序列 : 5 2 2 8 3
第一趟排序: 2 2 5 3 8
第二趟排序: 2 2 3 5 8
第三趟排序: 2 2 3 5 8
当排序结束后,我们发现加粗的2,一直是在2的前面,所以可以说明,冒泡排序是稳定的
3. 冒泡排序的复杂度分析
(1)最好的情况: 当需要排序的序列的表本身就是有序的,那么比较次数可以推断出进行n-1 次比较,没有数据交换,时间复杂度是O(n)
(2)最坏的情况 : 即待排序的表是逆序的情况,此时需要比较1+2+3+4…+(n-1) =n(n-1)/2次 , 即时间复杂度为 O(n**2)
(3)稳定性: 稳定
三. 快速排序
1. 基本思想
通过一趟排序将待排记录分割成独立的两部分,其中一部分记录的关键字均比另一部分的关键字小,则可分别对这两部分记录分别排序,以达到整个序列有序的目的。
代码实现:
def quicksort(arr):
# 如果该数组为空,或者只有一个元素
if len(arr) < 2:
return arr
else:
# 递归的条件,选择需要做基准的关键字
mid = arr[0]
# 是所有小于关键字的组合
less = [i for i in arr[1:] if i <= mid]
# 是所有大于关键字的组合
greater = [i for i in arr[1:] if i > mid]
# 返回整个序列
return quicksort(less) + [mid] + quicksort(greater)
print(quicksort([10, 5, 2, 3,13]))
运行结果为:
2. 时间复杂度
最坏情况: 时间复杂度 O(n^2)
平均时间复杂度:
稳定性:不稳定
四. 直接插入排序
1. 基本操作
直接插入排序的基本操作是将一个记录插入到已经安排好的有序序列中中,从而得到一个新的记录数增加一的有序表。
每次将排序中的元素,插入到前面已经排好的有序序列中间去,知道排序完成。
步骤:
1) a[0] 是有序区,待排序的是a[1…n-1] . 令 i =1
2) 将a[1] 与a[0] 中的元素进行比较,将小的元素放在第一个位置
3) 以此类推,知道待排序列中全部元素都插入完成为止
代码实现:
def insert_sort(nums):
"""
# 插入排序
:param nums:
:return:
"""
count = len(nums) # 待排序的序列
# # 第一个索引认为是有序的数值序列, 依次遍历除了第一个元素之外的其他元素, 插入到前面有序的序列
for i in range(1, count):
# 要插入的数值元素
key = nums[i]
# 和有序表的最后一个元素开始比较(此时j=i-1),
# 直到比较到有序序列的第一个元素时结束(此时j=0)
j = i - 1
while j >= 0:
# 比较大小
if nums[j] > key:
nums[j + 1] = nums[j]
nums[j] = key
j -= 1
return nums
if __name__ == '__main__':
nums = [5, 4, 3, 2, 1]
sort_nums = insert_sort(nums)
print(sort_nums)
运行结果为:
2. 时间复杂度
最好的情况: 需要排序的本身序列就是有序的,时间复杂度为O(n)
最坏的情况: 需要排序的是逆序的情况,时间复杂度是O(n**2)
稳定性:稳定
五. 希尔排序
1. 基本思想
算法先将需要排序的一组数按照某个增量d (n/2,n为需要排序的个数)分成若干组,每组中记录的再用一个较小的增量(d/2)对它进行分组,在每组中进行直接插入排序。当增量减少到1 的时候,进行直接排序后,排序完成。
代码实现:
def insert_sort(nums):
"""
# 插入排序
:param nums:
:return:
"""
count = len(nums) # 待排序的序列
# # 第一个索引认为是有序的数值序列, 依次遍历除了第一个元素之外的其他元素, 插入到前面有序的序列
for i in range(1, count):
# 要插入的数值元素
key = nums[i]
# 和有序表的最后一个元素开始比较(此时j=i-1),
# 直到比较到有序序列的第一个元素时结束(此时j=0)
j = i - 1
while j >= 0:
# 比较大小
if nums[j] > key:
nums[j + 1] = nums[j]
nums[j] = key
j -= 1
return nums
def shellSort(arr):
"""
5 4 3 2 1
"""
step = int(len(arr) / 2) # step
while step > 0:
print("step=", step)
arr_len = len(arr)
# index = 0 1 2 3 4
for index in range(arr_len):
# index=0 index+step=3
# index=1 indx+step=4
if index + step < arr_len:
current_val = arr[index]
if current_val > arr[index + step]:
arr[index], arr[index + step] = arr[index + step], arr[index]
step = int(step / 2)
else:
# 直接插入排序
return insert_sort(arr)
arr = [12, 34, 54, 2, 3, 4, 5, 2, 1, 44]
sorted_arr = shellSort(arr)
print(sorted_arr)
实验结果如下:
2. 时间复杂度
最好情况: O(n log^2 n)
最坏情况: O(n log^2 n )
平均时间复杂度: O(n log n )
稳定性:不稳定
六. 选择排序
1. 基本思想
选择排序是一种简单直观的排序算法。首先在末排序序列中找到最小(大)元素,存放到排序序列的起始位置,再从剩余末排序元素中继续寻找最小(大)元素,然后 放到已经排序序列的末尾。以此类推,直到所有元素均排序成功
代码实现:
def findSmallest(arr):
smallest = arr[0]
smallest_index = 0
for i in range(1, len(arr)):
if arr[i] < smallest:
smallest = arr[i]
smallest_index = i
return smallest_index
def selectSort(arr):
newArr = []
for i in range(len(arr)):
smallest = findSmallest(arr)
newArr.append(arr.pop(smallest))
return newArr
print(selectSort([1, 4, 3, 5, 7, 4, 2]))
运行结果为:
2. 时间复杂度
最好情况: O(n^2)
最坏情况: O(n^2)
稳定性: 不稳定 : 因为考虑升序每次选择最大的情况
七. 归并排序
1.基本思想
归并排序是建立在归并操作上的一种有效的排序算法。 该算法采用的是分治法
分治法:
分割: 递归地将当前的序列平均分成两半
集成: 在保持元素顺序地同时将上一步得到地子序列集成到一起(归并)
eg :
2. 时间复杂度
最好情况: O(n log n )
最坏情况: O(n log n )
平均时间复杂度: O(n log n )
稳定性: 稳定
八. 基数排序
1. 基本思想
基数排序,是通过键值地部分资讯,将要排序的元素分配到某些“桶”中,达到排序的作用,基数排序法属于稳定性的排序。
代码实现:
def RadixSort(Lst):
d = len(str(max(Lst))) # 列表中的最大元素的位数
for i in range(d): # 0-9一共10个桶
BucketLst = [[] for k in range(10)]
for j in range(len(Lst)):
BucketLst[Lst[j] // (10 ** i) % 10].append(Lst[j]) # 10**i是关注意点,之前一直是10**d,debug才发现
Lst = [number for B in BucketLst for number in B] # 关键2-依次拿出桶中的元素,给原列表Lst,
# 而不是给缓存 temp = [number for B in BucketLst for number in B]
return Lst
if __name__ == "__main__":
Lst1 = [810, 700, 821, 892, 846, 199]
print(RadixSort(Lst1))
实验结果为:
eg.
对{50 , 123,543,187,49,30,0,2,11,100} 进行基数排序
2. 时间复杂度
最好情况:O(N+K)
最坏情况: O(N+K)
平均时间复杂度:O(N+K)
稳定性: 稳定