本文是学习陆老师的《python全栈工程师 - 数据结构与算法》课程的笔记,欢迎学习交流。同时感谢陆老师的精彩传授!
一、课程目标
- 选择排序
- 冒泡排序
- 插入排序
- 快速排序
- 合并排序
二、详情解读
01选择排序:
举例: [5, 2, 4 , 3, 1]
1.排序目的是在一个排好序的序列里面,从左到右是从小到大排序的,比如[1,2,3,4,5]
2.选择一个元素,然后与所有其他尚未排序元素比较,如果最小与左侧交换位置
3.第一轮:[1,2,4,3,5]
4.第二轮:[1,2,4,3,5]
5.第三轮:[1,2,3,4,5]
def select_sort(lists):
'''
选择排序
'''
max_index = len(lists)
for i in range(max_index):
min_index = i
for j in range(i+1, max_index):
if lists[min_index] > lists[j]:
min_index = j
if min_index != i:
print('min_index=', min_index, ' ', lists[min_index], '<=>', lists[i])
lists[i], lists[min_index] = lists[min_index], lists[i]
print(lists)
print('-'*100)
return lists
lists = list(range(1, 6))
from random import shuffle
shuffle(lists)
print(lists)
print('end=>', select_sort(lists))
运行结果:
时间复杂度
1.选择排序包含了两个循环
2.总的次数是(n-1)+ (n - 2)+ … + 1
3.因此时间复杂度为n(n-1)/2, 即O(n^2)
02.冒泡排序:
举例:[5,2,4,3,1]
1.从第一个元素开始,每个元素与后面的一个元素比较,较大的排到后面
2.第一轮: [2,4,3,1,5]
3.第二轮:[2,3,1,4,5]
4.第三轮:[2,1,3,4,5]
5.第四轮:[1,2,3,4,5]
代码实现:
from random import shuffle
def bubble_sort(lists):
'''
冒泡排序
'''
max_index = len(lists) - 1
for i in range(max_index):
for j in range(max_index-i):
if lists[j] > lists[j+1]:
lists[j], lists[j+1] = lists[j+1], lists[j]
print('i=', i, ':', 'j=', j, '=>', lists)
return lists
lists = list(range(1, 6))
shuffle(lists)
print(lists)
print('end=>', bubble_sort(lists))
运行结果:
时间复杂度
1.冒泡排序包含了两个循环
2.总的次数是(n-1)+ (n - 2)+ … + 1
3.因此时间复杂度为n(n-1)/2, 即O(n^2)
03插入排序
1.插入排序从第2个元素开始排序,与该元素之前的元素比较
2.该元素之前的顺序已经排好
3.如果该元素顺序可以插入的话,将该元素按照顺序插入
举例: [5,2,4,3,1]
1.从4开始排,4之前的顺序片段:[5],4如果加入到这个片段,需要插入到5之前
i=1:j => 0 [5,5,3,1,2] - 将5移后一位,让出位置
i=1:j => -1 [4,5,3,1,2] - 将4插入到5之前
2.排3的时候,3之前的顺序片段:[4,5],3如果加入到这个片段,需要插入到4之前
3.将[4,5]后移
i = 2: j => 1 [4, 5, 6, 1, 2]
i = 2: j => 0 [4,4,5,1,2]
i = 2: j => -1 [3,4,5,1,2]
代码实现:
from random import shuffle
def insert_sort(lists):
'''
插入排序
'''
for i in range(1, len(lists)):
# 准备被插入排序的元素
item_to_insert = lists[i]
# 将这个元素与它之前排好顺序的元素比较
for j in range(i-1, -2, -1):
# 如果这个元素比它前面的元素小,就把它插入进去
# 插入过程就是前面的元素一个个后移,直到大于其中的某个元素
if item_to_insert < lists[j]:
lists[j+1] = lists[j]
else:
break
# 插入空出来的位置
# 在 j 位置的元素比等待插入的元素要小,所以插入位置为 j+1
lists[j+1] = item_to_insert
return lists
l = [i for i in range(1, 20)]
shuffle(l)
print(l)
print('end=>', insert_sort(l))
运行结果:
时间复杂度:
1.插入排序包含了两个循环
2.总的次数是(n - 1) + (n - 2 )+ … + 1
3.因此时间复杂度为n(n-1)/2, 即 O(n^2)
04.快速排序
举例:[5,2,4,3,1]
1.找一个基准点,比如4,
2.以4交换到最后一项[5,2,3,1,4]
3.在列表开始处设立一个边界标志,比如现在[:5,2,3,1,4]的5左侧:为边界标志
4.小于4的项放到边界左侧,直到所有小于4的放到边界左侧,每在边界左侧放置一个,边界位置右移一次,边界右侧的都比4大
5.[2,3,1,:5,4]
6.然后将4放置边界标志:的左侧 [2,3,1,4,:5]
7.在边界两侧重复以上过程,直到分组里只有一个元素
举例:[5,2,4,3,1]
1.第二轮:[2, 3, 1][4][5]
通过第一轮,将列表分成了左右两部分,左边的是[2,3,1,4],右边是[5],右边只剩下一个,就不用排了
2.在[2,3,1]中选3,那么[2,3,1],2就是边界标志
3.将3移到最右边[2,1,3]
4.[:2,1,3],然后将1放置于2的左侧,[1,:2,3],2也移到边界左边,边界移到[1,2,:3],[1,:2,3]就排好了
代码实现:
def quicksort(lists):
# 初始左边界为0,右边界为列表上界
quick_sort_helper(lists, 0, len(lists)-1)
return lists
def quick_sort_helper(lists, left, right):
# 边界左边不能超越边界右边
# print('left=', left, 'right=', right)
if left < right:
# 分割列表设置分界点
boundary = partition(lists, left, right)
# 分界点左侧接着分割
quick_sort_helper(lists, left, boundary-1)
# 分界点右侧接着分割
quick_sort_helper(lists, boundary+1, right)
def partition(lists, left, right):
# 根据left, right寻找分界点
middle = (left + right) // 2
# 将分界点放置于该段最右侧
lists[right], lists[middle] = lists[middle], lists[right]
boundary = left
# 将小于分界点的排到左边,边界右移+1
for i in range(left, right):
print(lists)
if lists[i] < lists[right]:
lists[i], lists[boundary] = lists[boundary], lists[i]
boundary += 1
# 再将分界点移回
lists[right], lists[boundary] = lists[boundary], lists[right]
return boundary
lists = list(range(1, 6))
from random import shuffle
shuffle(lists)
print(lists)
print(quicksort(lists))
运行结果:
时间复杂度:
1.第一次是O(n)
2.由于每次是分割成2部分,大概log2n次可以得到一个元素
3.最好的水平是O(nlog2n)
4.最坏的是每个元素都作为一次分割点,O(n^2)
05.合并排序
举例:[5, 2, 4, 3, 1]
1.选一个点将列表分成左右3部分[2, 3, 1] + [4] + [5]
2.左边的列表所有的元素< 分割点
3.右边的列表所有的元素> 分割点
4.将左边的部分[2, 3, 1]重复1~3步骤[2, 1] + [3] + []
5.将[2, 1]重复1~3步骤[] + [1] + [2]
6.通过递归将他们合并起来[] + [1] + [2] + [3] + [] + [4] + [5]
代码实现:
def merge_sort(lists):
if len(lists) <= 1:
return lists
middle = len(lists) // 2
left_lists = []
right_lists = []
for i in lists[:middle]:
if i < lists[middle]:
left_lists.append(i)
else:
right_lists.append(i)
for i in lists[middle+1:]:
if i < lists[middle]:
left_lists.append(i)
else:
right_lists.append(i)
print(left_lists, right_lists)
return merge_sort(left_lists) + [lists[middle]] + merge_sort(right_lists)
lists = list(range(1, 10))
from random import shuffle
shuffle(lists)
print(lists)
print('end=>', merge_sort(lists))
运行结果:
时间复杂度
1.由于每次是分割成2部分,大概log2n次可以得到一个元素
2.合并的次数是O(n)
3.平均水平是O(nlog2n)
4.由于使用了递归与两个列表,所以空间复杂度增大O(logn) + O(n)
06.各排序方法的对比
排序方法 | 平均时间 | 最坏时间 | 辅助空间 | 稳定性 |
---|---|---|---|---|
选择排序 | O(n^2) | O(n^2) | O(1) | 不稳定 |
冒泡排序 | O(n^2) | O(n^2) | O(1) | 稳定 |
插入排序 | O(n^2) | O(n^2) | O(1) | 稳定 |
快速排序 | O(nlogn) | O(n^2) | O(logn) | 不稳定 |
合并排序 | O(nlogn) | O(nlogn) | O(n) | 稳定 |
三、课程小结
- 学习了排序算法
- 学习了排序代码实现
- 学习了如何进行排序时间复杂度分析