常见的排序算法有:插入排序、希尔排序、选择排序、冒泡排序、归并排序、快速排序、堆排序、基数排序等。
稳定性:
-
排序后 2 个相等键值的顺序和排序之前它们的顺序相同
-
稳定的排序算法:冒泡排序、插入排序、归并排序和基数排序。
-
不是稳定的排序算法:选择排序、快速排序、希尔排序、堆排序。
测试:
单位(s) | 10000数据 | 100000数据 | 1000000 |
---|---|---|---|
冒泡排序 | 21.00 | / | / |
选择排序 | 8.02 | / | / |
插入排序 | 8.00 | / | / |
堆排序 | 0.096 | 1.094 | 16.209 |
归并排序 | 0.087 | 1.017 | 13.214 |
快速排序 | 0.03 | 0.614 | 7.659 |
sorted() | 0.00 | 0.312 | 0.752 |
1.冒泡排序:核心思想就是每次比较两个相邻的数,把数大的放后面,每循环一次找出一个最大的数放到要排序的列表的最后面,然后循环排序,直到没有任何一对数字需要比较,要排序的列表就排好序了。
def bubbleSort(arr):
for i in range(1,len(arr)):
for j in range(0,len(arr)-i):
if arr[j]>arr[j+1]:
arr[j],arr[j+1]=arr[j+1],arr[j]
(2).动图演示:
2.选择排序:核心思想就是找到要排序列表中最小的那个数,把它放到第一位,然后再动列表中找到最小的数把它放到已排好序的列表后面,依次排完。选择排序是一种简单直观的排序算法,无论什么数据进去都是 O(n²) 的时间复杂度。所以用到它的时候,数据规模越小越好。唯一的好处可能就是不占用额外的内存空间。
#找到最小值的下标
def get_min_pos(arr):
min_pos = 0
for i in range(1,len(arr)):
if arr[i] < arr[min_pos]:
min_pos = i
return min_pos
def select_sort(arr):
for i in range(len(arr)-1):
min_pos = i
for j in range(i+1,len(arr)):
if arr[j] < arr[min_pos]:
min_pos = j
arr[i],arr[min_pos] = arr[min_pos],arr[i]
(2):动图演示:
3.插入排序:插入排序就像我们打扑克牌一样,你先接一张拿到手里,再接一张,如果比手里的大,放到右边,如果比手里小,放在左边,把每张牌插入到它应该在的位置上。手里的牌是排好序的,未接的牌是未排序的,直到牌接完就排序好了。
def insert_sort(arr):
for i in range(1,len(arr)):
# 拿到的第一张牌
tmp = arr[i]
j = i - 1
while j >= 0 and arr[j] > tmp: #只要往后挪就循环 2个条件都得满足
arr[j+1] = arr[j] #如果j =-1 停止挪 如果arr[j]小了,停止挪
j -= 1
arr[j+1] = tmp
(2):动图演示:
4.快速排序:快排的思想就是拿到要排序列表的第一个元素,经过算法找到它在列表中的排序好后的位置,然后将列表分成左右两个列表,左右两个列表再递归的排序。递归结束条件就是要排序的这个列表只剩1个元素,或0个元素就结束了。left是列表中第一个元素的小标,right是列表中最后一个元素的下标left = 0 right = len(arr-1)。
代码:
def quick_sort(arr,left,right):
if left <right: #待排序的区域至少有两个元素
mid = partition(arr,left,right)
quick_sort(arr,left,mid-1)
quick_sort(arr,mid+1,right)
return arr
def partition(arr,left,right):
tmp = arr[left] #把要排序列表的第一个数存起来
while left < right:
while left < right and arr[right] >= tmp:
right -= 1
arr[left] = arr[right]
while left < right and arr[left] <= tmp:
left += 1
arr[right]=arr[left]
arr[left] =tmp
return left
(2):动图演示:
5.堆排序:
堆的性质:堆分为大根堆和小根堆,堆是一个完全二叉树。
- 大根堆:一颗完全二叉树,满足任意节点都比其孩子节点大。大根堆排序出来是升序的。
- 小根堆:一颗完全二叉树,满足任意节点都比其孩子节点小。小根堆排序出来是降序的。
- 核心思想就是每次把堆顶最大的元素拿下来(d大根堆),把子节点最小元素放到堆顶(拿到最小元素后堆依然是完全二叉树),然后再依次调整堆,让它满足堆的性质,然后再取下堆顶最大元素,依次排完。
#low是树的根的位置下标,high是树最后一个元素的下标
def sift(arr,low,high):
tmp = arr[low]
# i指向空位置,j指向两个孩子
i = low
j = 2*i + 1
#循环对出的第二种情况,j>high,说明位置i是叶子节点
while j <= high:
#如果右孩子存在并且比左孩子大,指向右孩子
if j+1 <= high and arr[j] < arr[j+1]:
j += 1
if arr[j] > tmp:
arr[i] = arr[j]
i = j
j = 2 * i +1
#循环退出的第一种情况:j位置的值比tmp小,说明两个孩子都比tmp小
else:
break
arr[i] = tmp
def heap_sort(arr):
n = len(arr)
#构造堆
for low in range(n//2-1,-1,-1):
sift(arr,low,n-1)
#挨个出数
for high in range(n-1,-1,-1):
arr[0],arr[high] = arr[high],arr[0]
sift(arr,0,high-1)
(2):动图演示:
6.归并排序:核心思想就是将要排序的序列递归分成最小的序列(序列中只剩两个元素或1个元素),然后比较最小序列中的两个数,小的放左边,大的放右边,最小序列就拍好序了,然后再递归往上,两个排序好的最小序列再比较其中元素大小,拍好序之后再递归往上比较。直到递归结束完成排序。
def merge_sort(arr):
if len(arr) <= 1:
return arr
num = len(arr)//2
left = merge_sort(arr[:num])
right = merge_sort(arr[num:])
return _sort(left,right)
def _sort(left,right):
l,r = 0,0
result = []
while l < len(left) and r < len(right):
if left[l] < right[r]:
result.append(left[l])
l += 1
else:
result.append(right[r])
r += 1
result +=left[l:]
result +=right[r:]
return result
(2):动图演示:
7.希尔排序:核心思想就是先将整个待排序的记录序列分割成为若干子序列分别进行直接插入排序,待整个序列中的记录“基本有序”时,再对全体记录进行依次直接插入排序。
def insert_sort_gro(arr,d):
for i in range(d,len(arr)):
# 拿到的第一张牌
tmp = arr[i]
j = i - d
while j >= 0 and arr[j] > tmp: #只要往后挪就循环 2个条件都得满足
arr[j+d] = arr[j] #如果j =-1 停止挪 如果arr[j]小了,停止挪
j -= d
arr[j+d] = tmp
def shell_sort(arr):
d = len(arr) // 2
while d>0:
insert_sort_gro(arr,d)
d = d // 2
8.计数排序:核心思想就是将输入的数据值转化为键存储在额外开辟的数组空间中。然后遍历要排序的列表,记录每个元素出现的次数,最后根据记录的次数依次打印列表。计数排序时间复杂度是n,很快,但是它有个缺陷,就是要排序的列表中的元素必须是一个区间的数,而这个区间跨度不能很大,比如[1,20000000,3,6,3,45,6],对于种列表,计数排序就跪下了。
def count_sort(arr,max_num):
count = [0 for _ in range(max_num + 1)]
for val in arr:
count[val] += 1
arr.clear()
for i,v in enumerate(count):
for _ in range(v):
arr.append(i)
(2):动图演示: