文章目录
1. 算法分类
1.1 分类
排序算法大致可以分为两类:
- 比较类排序:通过比较来决定元素间的相对次序,由于其时间复杂度不能突破O(nlogn),因此也称为非线性时间比较类排序。
- 非比较类排序:不通过比较来决定元素间的相对次序,它可以突破基于比较排序的时间下界,以线性时间运行,因此也称为线性时间非比较类排序。
相关概念
- 稳定:如果a原本在b前面,而a=b,排序之后a仍然在b的前面。
- 不稳定:如果a原本在b的前面,而a=b,排序之后 a 可能会出现在 b 的后面。
- 时间复杂度:对排序数据的总的操作次数。反映当n变化时,操作次数呈现什么规律。
- 空间复杂度:是指算法在计算机内执行时所需存储空间的度量,它也是数据规模n的函数。
算法复杂度
算法的复杂度可以参考下图:
2. 算法介绍与实现
2.1 冒泡排序(Bubble Sort)
冒泡排序是一种简单的排序算法。它重复地走访过要排序的数列,一次比较两个元素,如果它们的顺序错误就把它们交换过来。走访数列的工作是重复地进行直到没有再需要交换,也就是说该数列已经排序完成。这个算法的名字由来是因为越小的元素会经由交换慢慢“浮”到数列的顶端
。
2.1.1 算法描述
- 比较相邻的元素。如果第一个比第二个大,就交换它们两个;
- 对每一对相邻元素作同样的工作,从开始第一对到结尾的最后一对,这样在最后的元素应该会是最大的数;
- 针对所有的元素重复以上的步骤,除了最后一个;
- 重复步骤1~3,直到排序完成。
2.1.2 动图演示
2.1.3 Python实现
# -*- coding: UTF-8 -*-
import random
def random_int_list(start, stop, length):
start, stop = (int(start), int(stop)) if start <= stop else (int(stop),
int(start))
length = int(abs(length)) if length else 0
random_list = []
for i in range(length):
random_list.append(random.randint(start, stop))
return random_list
def bubble_sort(alist):
"""
冒泡排序
最优时间复杂度:O(n) (表示遍历一次发现没有任何可以交换的元素,排序结束)
最坏时间复杂度:O(n^2)
稳定性:稳定
"""
n = len(alist)
# 结算列表的长度
n = len(alist)
# 外层循环控制从头走到尾的次数
for i in range(n - 1):
# 用一个count记录一共交换的次数,可以排除已经是排好的序列
count = 0
# 内层循环控制走一次的过程
for j in range(0, n - 1 - i):
# 如果前一个元素大于后一个元素,则交换两个元素(升序)
if alist[j] > alist[j + 1]:
# 交换元素
alist[j], alist[j + 1] = alist[j + 1], alist[j]
# 记录交换的次数
count += 1
# count == 0 代表没有交换,序列已经有序
if 0 == count:
break
return alist
if __name__ == "__main__":
for i in range(random.randint(10, 20)):
_list = random_int_list(-100, 100, random.randint(1, 20))
sorted_list = bubble_sort(_list)
print(sorted_list == sorted(_list), sorted_list)
2.2 选择排序(Selection Sort)
选择排序(Selection-sort)是一种简单直观的排序算法。它的工作原理:首先在未排序序列中找到最小(大)元素,存放到排序序列的起始位置
,然后,再从剩余未排序元素中继续寻找最小(大)元素,然后放到已排序序列的末尾。以此类推,直到所有元素均排序完毕。
2.2.1 算法描述
n个记录的直接选择排序可经过n-1趟直接选择排序得到有序结果。具体算法描述如下:
- 初始状态:无序区为R[1…n],有序区为空;
- 第i趟排序(i=1,2,3…n-1)开始时,当前有序区和无序区分别为R[1…i-1]和R(i…n)。该趟排序从当前无序区中-选出关键字最小的记录 R[k],将它与无序区的第1个记录R交换,使R[1…i]和R[i+1…n)分别变为记录个数增加1个的新有序区和记录个数减少1个的新无序区;
- n-1趟结束,数组有序化了。
2.2.2 动图演示
2.2.3 Python实现
# -*- coding: UTF-8 -*-
import random
def random_int_list(start, stop, length):
start, stop = (int(start), int(stop)) if start <= stop else (int(stop),
int(start))
length = int(abs(length)) if length else 0
random_list = []
for i in range(length):
random_list.append(random.randint(start, stop))
return random_list
def selection_sort(alist):
"""
选择排序
最优与最坏时间复杂度都是:O(n^2)
稳定性:稳定
"""
# 结算列表的长度
n = len(alist)
# 外层循环控制从头走到尾的次数
for i in range(n - 1):
# 假定最小的数的下标是i
min_index = i
for j in range(i + 1, n):
# 如果后面的数比假定的最小数还大,就更新这个index值
if alist[min_index] > alist[j]:
min_index = j
# 如果min_index更新了,就把最小值放到前面
if i != min_index:
alist[i], alist[min_index] = alist[min_index], alist[i]
return alist
if __name__ == "__main__":
for i in range(random.randint(10, 20)):
_list = random_int_list(-100, 100, random.randint(1, 20))
# sorted_list = bubble_sort(_list)
sorted_list = selection_sort(_list)
print(sorted_list == sorted(_list), sorted_list)
2.3 插入排序(Insertion Sort)
插入排序(Insertion-Sort)的算法描述是一种简单直观的排序算法。它的工作原理是通过构建有序序列,对于未排序数据,在已排序序列中从后向前扫描,找到相应位置并插入。
2.3.1 算法描述
一般来说,插入排序都采用in-place在数组上实现。具体算法描述如下:
- 从第一个元素开始,该元素可以认为已经被排序;
- 取出下一个元素,在已经排序的元素序列中从后向前扫描;
- 如果该元素(已排序)大于新元素,将该元素移到下一位置;
- 重复步骤3,直到找到已排序的元素小于或者等于新元素的位置;
- 将新元素插入到该位置后;
- 重复步骤2~5。
2.3.2 动图演示
2.3.3 Python实现
# -*- coding: UTF-8 -*-
import random
def random_int_list(start, stop, length):
start, stop = (int(start), int(stop)) if start <= stop else (int(stop),
int(start))
length = int(abs(length)) if length else 0
random_list = []
for i in range(length):
random_list.append(random.randint(start, stop))
return random_list
def insert_sort(alist):
"""
插入排序:假定左边都是有序的,将右边的数一个一个插入到左边有序数列中
"""
# 结算列表的长度
n = len(alist)
# 外层循环控制从头走到尾的次数
for i in range(n - 1):
preIndex = i - 1
# 将要比较的数
current = alist[i]
# 在左边有序数列总找到所有比当前数更大的数
while preIndex >= 0 and alist[preIndex] > current:
# 元素后移
alist[preIndex + 1] = alist[preIndex]
# 往前遍历
preIndex -= 1
# 找到插入位置后,插入
alist[preIndex + 1] = current
return alist
if __name__ == "__main__":
for i in range(random.randint(10, 20)):
_list = random_int_list(-100, 100, random.randint(1, 20))
# sorted_list = bubble_sort(_list)
# sorted_list = selection_sort(_list)
sorted_list = insert_sort(_list)
print(sorted_list == sorted(_list), sorted_list)
2.4 希尔排序(Shell Sort)
1959年Shell发明,第一个突破O(n2)的排序算法,是简单插入排序的改进版。它与插入排序的不同之处在于,它会优先比较距离较远的元素。希尔排序又叫缩小增量排序
2.4.1 算法描述
先将整个待排序的记录序列分割成为若干子序列分别进行直接插入排序,具体算法描述:
- 选择一个增量序列t1,t2,…,tk,其中ti>tj,tk=1;
- 按增量序列个数k,对序列进行k 趟排序;
- 每趟排序,根据对应的增量ti,将待排序列分割成若干长度为m 的子序列,分别对各子表进行直接插入排序。仅增量因子为1 时,整个序列作为一个表来处理,表长度即为整个序列的长度。
2.4.2 演示
2.4.3 Python实现
def shell_sort(alist):
"""
希尔排序:类似于分组的插入排序
"""
# 结算列表的长度
n = len(alist)
gap = n >> 1 # 这里用 n//2(整除2)也可以
while gap > 0:
# 从第gap个元素,逐个对其所在组进行直接插入排序操作
for i in range(gap, n):
temp = i
while (temp - gap) >= 0 and alist[temp] < alist[temp - gap]:
alist[temp], alist[temp - gap] = alist[temp - gap], alist[temp]
temp -= gap
gap >>= 1
return alist
2.5 归并排序(Merge Sort)
归并排序是建立在归并操作上的一种有效的排序算法。该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为2-路归并。
2.5.1 算法描述
- 把长度为n的输入序列分成两个长度为n/2的子序列;
- 对这两个子序列分别采用归并排序;
- 将两个排序好的子序列合并成一个最终的排序序列。
2.5.2 图演示
2.5.3 Python实现
def merge_sort(alist):
n = len(alist)
if n <= 1:
return alist
mid = n >> 2
left = merge_sort(:mid)
right = merge_sort(mid:)
merge(left, right)
def merge(left, right):
i,j = 0, 0
res = []
while i < len(left) and j < len(right):
if left[i] <= left[right]:
res.append(left[i])
left += 1
else:
res.append(right[j])
right += 1
res.append(left[i:])
res.append(right[j:])
return res
2.6 快速排序(Quick Sort)
快速排序的基本思想:通过一趟排序将待排记录分隔成独立的两部分,其中一部分记录的关键字均比另一部分的关键字小,则可分别对这两部分记录继续进行排序,以达到整个序列有序。
2.6.1 算法描述
快速排序使用分治法来把一个串(list)分为两个子串(sub-lists)。具体算法描述如下:
1.在待排序的元素任取一个元素作为基准(通常选第一个元素,但最的选择方法是从待排序元素中随机选取一个作为基准),称为基准元素;
2.将待排序的元素进行分区,比基准元素大的元素放在它的右边,比其小的放在它的左边;
3.对左右两个分区重复以上步骤直到所有元素都是有序的。
2.6.2 图演示
基准数为8,以基准数为8划分成左右两部分,这里右边部分没有,只有左边部分
基准数为5,以基准数为5划分成左右两部分
2.6.3 Python实现
def quick_sort(array, left, right):
if left == right:
return array
if left > right:
return
low = left
high = right
key = array[low]
while left < right:
# 找到右边第一个小于于key的数值下标
while left < right and array[right] > key:
right -= 1
# 左拆右补
array[left] = array[right]
# 找到左边第一个大于等于于key的数值下标
while left < right and array[left] <= key:
left += 1
# 右拆左补
array[right] = array[left]
array[right] = key
quick_sort(array, low, left - 1)
quick_sort(array, left + 1, high)
return array
if __name__ == "__main__":
for i in range(random.randint(10, 20)):
_list = random_int_list(-100, 100, random.randint(1, 20))
sorted_list = quick_sort(_list, 0, len(_list) - 1)
print(sorted_list == sorted(_list), sorted_list)
2.7堆排序(Heap Sort)
堆排序是利用堆这种数据结构而设计的一种排序算法,堆排序是一种选择排序,它的最坏,最好,平均时间复杂度均为O(nlogn)。
堆(heap)也被称为优先队列(priority queue)。队列中允许的操作是先进先出(FIFO),在队尾插入元素,在队头取出元素。而堆也是一样,在堆底插入元素,在堆顶取出元素,但是堆中元素的排列不是按照到来的先后顺序,而是按照一定的优先顺序排列的。这个优先顺序可以是元素的大小或者其他规则。如图一所示就是一个堆,堆优先顺序就是大的元素排在前面,小的元素排在后面,这样得到的堆称为最大堆。最大堆中堆顶的元素是整个堆中最大的,并且每一个分支也可以看成一个最大堆。同样的,我们可以定义最小堆。如图所示
2.7.1 算法描述
堆排序的基本思想是:将待排序序列构造成一个大顶堆,此时,整个序列的最大值就是堆顶的根节点。将其与末尾元素进行交换,此时末尾就为最大值。然后将剩余n-1个元素重新构造成一个堆,这样会得到n个元素的次小值。如此反复执行,便能得到一个有序序列了。
2.7.2 图示
步骤一、 构造初始堆。将给定无序序列构造成一个大顶堆(一般升序采用大顶堆,降序采用小顶堆)。
假设给定无序序列结构如下图示
.调整后得到一个大顶堆,如下图所示
步骤二、 将堆顶元素与末尾元素进行交换,使末尾元素最大。然后继续调整堆,再将堆顶元素与末尾元素交换,得到第二大元素。如此反复进行交换、重建、交换。
1、将堆顶元素9和末尾元素4进行交换
2、重新调整结构,使其继续满足堆定义
3、再将堆顶元素8与末尾元素5进行交换,得到第二大元素8.
4、后续过程,继续进行调整,交换,如此反复进行,最终使得整个序列有序
再简单总结下堆排序的基本思路:
a.将无需序列构建成一个堆,根据升序降序需求选择大顶堆或小顶堆;
b.将堆顶元素与末尾元素交换,将最大元素"沉"到数组末端;
c.重新调整结构,使其满足堆定义,然后继续交换堆顶元素与当前末尾元素,反复执行调整+交换步骤,直到整个序列有序。
2.7.3 Python实现
2.6 快速排序(Quick Sort)
2.6.1 算法描述
2.6.2 动图演示
2.6.3 Python实现