一、列表排序概述
列表排序就是将无序列表转变为有序列表,包括升序和降序两种,python内置函数为sort()。
常见排序算法有冒泡排序、选择排序、插入排序、快速排序、堆排序、归并排序、希尔排序、计数排序和基数排序(9种)。
二、冒泡排序(Bubble Sort)
1、基本思路
(1)列表每两个相邻的数,如果前面比后面大,则交换两个数
(2)一趟排序完成之后,则无序区减少一个数,有序区增加一个数
(3)代码关键点:趟数、无序区范围
2、代码实现
def Bubble_Sort(li):
for i in range(len(li)-1): # 趟数
for j in range(len(li)-i-1): # 前后数比较
if li[j+1] > li[j]:
li[j], li[j+1] = li[j+1], li[j] # 交换位置
print("排序过程",li)
return li
print(Bubble_Sort([1,5,6,2,7]))
>>排序过程 [5, 1, 6, 2, 7]
>>排序过程 [5, 6, 1, 2, 7]
>>排序过程 [5, 6, 2, 1, 7]
>>排序过程 [5, 6, 2, 7, 1]
>>排序过程 [6, 5, 2, 7, 1]
>>排序过程 [6, 5, 7, 2, 1]
>>排序过程 [6, 7, 5, 2, 1]
>>排序过程 [7, 6, 5, 2, 1]
>>[7, 6, 5, 2, 1]
算法优化:如果冒泡排序中一趟排序没有发生交换,则说明列表已经有序,可以直接结束算法.
def Bubble_Sort2(li):
for i in range(len(li)-1):
exchange = False
for j in range(len(li)-i-1):
if li[j+1] > li[j]:
li[j], li[j+1] = li[j+1], li[j]
exchange = True
print("排序过程", li)
if not exchange:
return li
return li
print(Bubble_Sort2([7,5,3,1,2,3]))
>>排序过程 [7, 5, 3, 2, 1, 3]
>>排序过程 [7, 5, 3, 2, 3, 1]
>>排序过程 [7, 5, 3, 3, 2, 1]
>>[7, 5, 3, 3, 2, 1]
- 时间复杂度:O(n2)
- 稳定性:对于相等的两个元素不做交换处理,因此具有稳定性
三、选择排序(Select Sort)
1、基本思路
(1)一趟排序记录最小的数,放在第一个位置
(2)再一趟排序记录列表无序区最小的数,放在第二个位置
...
(3)算法关键点:有序区和无序区、无序区最小的数
2、代码实现
def Select_Sort(li):
for i in range(len(li)-1):
min_loc = i
for j in range(i+1, len(li)):
if li[j] < li[min_loc]:
min_loc = j
if min_loc != i:
li[i], li[min_loc] = li[min_loc], li[i]
print("当前排序",li)
return li
print(Select_Sort([5,8,4,1,6,7]))
>>当前排序 [1, 8, 4, 5, 6, 7]
>>当前排序 [1, 4, 8, 5, 6, 7]
>>当前排序 [1, 4, 5, 8, 6, 7]
>>当前排序 [1, 4, 5, 6, 8, 7]
>>当前排序 [1, 4, 5, 6, 7, 8]
>>[1, 4, 5, 6, 7, 8]
- 时间复杂度:O(n2)
- 稳定性:由于会进行选择,相同大小的元素如果出现在前面,会发生优先交换,因此该排序方法不稳定
排序前 | 3(1) | 5(1) | 3(2) | 2(1) |
---|---|---|---|---|
第一次排序 | 2(1) | 5(1) | 3(2) | 3(1) |
第二次排序 | 2(1) | 3(2) | 5(1) | 3(1) |
第三次排序 | 2(1) | 3(2) | 3(1) | 5(1) |
四、插入排序(Insert Sort)
1、基本思路
(1)初始时手里(有序区)只有一张牌
(2)每次(从无序区)取出一张牌,比较之后插入到手中已有牌的正确位置
2、代码实现
def insert_sort(li):
for i in range(1, len(li)): # i表示摸到的牌的下标
tmp = li[i]
j = i - 1 # j表示手里的牌的下标
while j >= 0 and tmp < li[j]: #循环比较前面的数
li[j+1] = li[j]
j = j - 1
li[j+1] = tmp
print("插入过程",li)
return li
li = [3,2,4,1,5,7,9,6,8]
print(insert_sort(li))
>>插入过程 [2, 3, 4, 1, 5, 7, 9, 6, 8]
>>插入过程 [2, 3, 4, 1, 5, 7, 9, 6, 8]
>>插入过程 [1, 2, 3, 4, 5, 7, 9, 6, 8]
>>插入过程 [1, 2, 3, 4, 5, 7, 9, 6, 8]
>>插入过程 [1, 2, 3, 4, 5, 7, 9, 6, 8]
>>插入过程 [1, 2, 3, 4, 5, 7, 9, 6, 8]
>>插入过程 [1, 2, 3, 4, 5, 6, 7, 9, 8]
>>插入过程 [1, 2, 3, 4, 5, 6, 7, 8, 9]
>>[1, 2, 3, 4, 5, 6, 7, 8, 9]
- 时间复杂度:O(n2)
- 稳定性:因为在两个数相等时,只会把较后面的数仍旧插在后面,因此是稳定的。
五、快速排序(Quick Sort)
1、基本思路
(1)取出一个元素p(第一个元素),使元素p归位
(2)列表被p分为两部分,左边都比p小,右边都比p大
(3)递归完成排序
2、代码实现
# 找出p的正确位置
def partition(data, left, right):
tmp = data[left]
while left < right:
while left < right and data[right] >= tmp:
right -= 1
data[left] = data[right]
while left < right and data[left] <= tmp:
left += 1
data[right] = data[left]
data[left] = tmp
return left
# 快速排序框架
def quick_sort(data, left, right):
if left < right:
mid = partition(data, left, right)
quick_sort(data, left, mid-1)
quick_sort(data, mid+1, right)
return data
li = [9,8,7,6,5,4,3,2,1]
print(quick_sort(li, 0, len(li)-1))
>>[1, 2, 3, 4, 5, 6, 7, 8, 9]
# 快速排序
def quickSort(nums, left, right):
'''
left = 0
right = len(nums)-1
'''
# 递归的终止条件
if left < right:
center = partion(nums, left, right)
quickSort(nums, left, center-1)
quickSort(nums, center+1, right)
return nums
# 排序之后寻找主元位置进行分割
def partion(nums, left, right):
i = left
for j in range(left, right):
if nums[j] < nums[right]:
nums[i], nums[j] = nums[j], nums[i]
i += 1
nums[i], nums[right] = nums[right], nums[i] # 交换主元
return i
nums = [9,8,7,6,5,4]
print(quickSort(nums, 0, len(nums)-1))
>>[4, 5, 6, 7, 8, 9]
3、时间复杂度讨论
(1)时间复杂度:O(nlogn)
(2)最坏情况下,从逆序排为正序,O(n2)
(3)稳定性:相等元素会和主元发生位置交换,因此不稳定
六、堆排序(Heap Sort)
1、什么是堆?
堆是一种特殊的完全二叉树结构
(1)大根堆:一颗完全二叉树,满足任一节点都比其孩子节点大
(2)小根堆:一颗完全二叉树,满足任一节点都比其孩子节点小
(3)堆的向下调整性质
假设根节点的左右子树都是堆,但根节点不满足堆的性质,可以通过一次向下调整将其变成一个堆
(4)二叉树的顺序存储方式
父节点与左孩子节点的下标关系:i --> 2i+1
父节点与右孩子节点的下标关系:i --> 2i+2
2、堆排序的过程
(1)建立堆
(2)得到堆顶元素,为最大元素
(3)去掉堆顶,将堆最后一个元素放到堆顶,此时可通过一次调整重新使堆有序
(4)堆顶元素为第二大元素
(5)重复步骤3,直到堆变空
3、代码实现
def sift(data, low, high):
"""
:param li: 列表
:param low: 堆的根节点位置
:param high: 堆的最后一个元素的位置
:return:
"""
i = low # i最开始指向根节点
j = 2 * i + 1 # j最开始是左孩子
tmp = data[i] # 把堆顶存起来
while j <= high: # 只要j位置有数
if j < high and data[j] < data[j + 1]: #如果右孩子存在且较大
j += 1 # j指向右孩子
if tmp < data[j]: # 堆顶小于右孩子
data[i] = data[j] # 把右孩子放在堆顶
i = j # 往下走一层
j = 2 * i + 1 # 左孩子
else: # tmp更大,把tmp放在i的位置上
li[i] = tmp # 把tmp放到某一级领导位置上
break
data[i] = tmp # 把tmp放到叶子节点上
# 排序
def heap_sort(data):
n = len(data)
for i in range(n//2-1, -1, -1): # i表示建堆时调整的部分根的下标
sift(data, i, n-1) # 建堆完成
for i in range(n-1, -1, -1): # i 表示当前堆的最后一个元素
data[0], data[i] = data[i], data[0]
sift(data, 0, i-1) # i-1 是新的high
li = [i for i in range(20)]
import random
random.shuffle(li)
print(li)
print(heap_sort(li))
>>[0, 17, 9, 14, 4, 16, 10, 7, 5, 12, 15, 2, 1, 19, 11, 3, 13, 6, 8, 18]
>>[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19]
- 时间复杂度:O(nlogn)
- 稳定性:不稳定
4、堆排序的内置模块
li = [i for i in range(20)]
import random
random.shuffle(li)
print(li)
import heapq
heapq.heapify(li) # 建堆
n = len(li)
for i in range(n):
print(heapq.heappop(li), end=',')
>>[0, 11, 17, 9, 2, 5, 4, 18, 1, 14, 15, 12, 6, 8, 7, 3, 19, 10, 16, 13]
>>0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,
5、使用堆排序解决topk问题
基本思路:
(1)取列表前k个元素建立一个小根堆,堆顶就是目前第k大的数
(2)依次向后遍历原列表,对于列表中的元素,如果小于堆顶,则忽略该元素;如果大于堆顶,则将堆顶更换为该元素,并且对堆进行一次调整
(3)遍历列表所有元素之后,倒序弹出堆顶
# 比较排序
def sift(li, low, high):
i = low
j = 2 * i + 1
tmp = li[low]
while j <= high:
if j + 1 <= high and li[j+1] < li[j]:
j = j + 1
if li[j] < tmp:
li[i] = li[j]
i = j
j = 2 * i + 1
else:
break
li[i] = tmp
def topk(li, k):
heap = li[0:k]
for i in range(k//2-1, -1, -1):
sift(heap, i, k-1)
# 1、建堆
for i in range(k, len(li)):
if li[i] > heap[0]:
heap[0] = li[i]
sift(heap, 0, k-1)
# 2、遍历
for i in range(k-1, -1, -1):
heap[0], heap[i] = heap[i], heap[0]
sift(heap, 0, i-1)
# 3、出数
return heap
import random
li = list(range(100))
random.shuffle(li)
print(topk(li, 10))
>>[14, 19, 21, 29, 30, 47, 65, 75, 84, 99]
七、归并排序(Merge Sort)
假设现在一个列表分两段有序,将其合并为一个有序列表
1、基本思路
(1)分解:将列表越分越小,直至分成一个元素
(2)终止条件:一个元素是有序的
(3)合并:将两个有序列表合并,列表越来越大
2、代码实现
(1)对于一个已经分别有序的列表,直接进行归并
# 一次排序
def merge(li, low, mid, high):
i = low
j = mid + 1
ltmp = []
while i <= mid and j <= high:
if li[i] <= li[j]:
ltmp.append(li[i])
i += 1
else:
ltmp.append(li[j])
j += 1
while i <= mid: # 右侧列表元素全部比较结束
ltmp.append(li[i])
i += 1
while j <= high: # 左侧列表元素全部比较结束
ltmp.append(li[j])
j += 1
li[low:high+1] = ltmp # 重新写入
li = [2,4,5,7,1,3,6,8]
merge(li, 0, 3, 7)
print(li)
>> [1, 2, 3, 4, 5, 6, 7, 8]
(2)对于一个乱序列表,则需要先将列表分解成部分有序
def mergeSort(nums, left, right):
'''
left = 0
right = len(nums)-1
'''
# 递归的终止条件是left=right,即分割为一个元素
if left < right:
mid = left + (right-left) // 2
mergeSort(nums, left, mid)
mergeSort(nums, mid+1, right)
merge(nums, left, mid, right)
return nums
def merge(nums, left, mid, right):
i = left
j = mid + 1
ltemp = []
while i <= mid and j <= right:
if nums[i] <= nums[j]:
ltemp.append(nums[i])
i += 1
else:
ltemp.append(nums[j])
j += 1
while i <= mid:
ltemp.append(nums[i])
i += 1
while j <= right:
ltemp.append(nums[j])
j += 1
nums[left:right+1] = ltemp
print(mergeSort([5,7,8,3,1,2,4,6], 0, 7))
>> [1, 2, 3, 4, 5, 6, 7, 8]
3、复杂度分析
时间复杂度:O(nlogn)
空间复杂度:O(n)
稳定性:因为在比较合并的过程中,当两个数相等时,先将前面的数加入数组,因此是稳定的。