本文总结数据结构中的几大排序算法,从原理、复杂度、稳定性和代码来逐步阐述。
首先明确一个概念:
稳定性:
如果一个排序算法是稳定的,当有两个相等键值的记录S和R,且在原本的列表中R是出现在S之前,则在排序过后的列表中R也将会是在S之前。
一、冒泡排序
原理:
对于列表任意相邻的两个元素进行比较,若前一个大于后一个,则两者进行交换,否则不交换。一趟冒泡下来,最大元素处在最后位置上,然后对前(n-1)个元素进行类似的操作,重复此过程,直到列表成为有序为止。
总共需要(n-1)次冒泡,第k次冒泡需要的比较次数为(n-k)
最优时间复杂度为:O(n)
最坏时间复杂度为:O(n^2)
稳定性:稳定
def bubble_sort(a_list):
for j in range(len(a_list) - 1, 0, -1): # 冒泡n-1次,每次两两进行比较
for i in range(j):
if a_list[i] > a_list[i + 1]:
a_list[i], a_list[i + 1] = a_list[i + 1], a_list[i]
二、选择排序
原理:
在未排序列中找最大元素,将其放到序列末尾,然后在从余下元素中继续寻找在大元素,将其移到末尾,以此类推,直到所有元素排完。
n个元素进行排序总共进行最多(n-1)次交换。
如果某元素位于正确的最终位置,则它不会被移动。
在多有依靠交换移动元素的排序方法中,选择排序属于非常好的一种。
最优时间复杂度:O(n^2)
最坏时间复杂度为:O(n^2)
空间复杂度:O(1)
稳定性:不稳定
def select_sort(a_list):
for i in range(len(a_list) - 1, 0, -1): # 最多进行n-1次交换
max_index = i # 假设末尾是最大元素
for j in range(i): # 在末尾以前的所有元素中选择最大元素,将其下标赋给j
if a_list[j] > a_list[max_index]:
max_index = j
if max_index != i: # 若最大元素不是最后一个,则和最大元素进行交换
a_list[i], a_list[max_index] = a_list[max_index], a_list[i]
三、插入排序
原理:
对于未排序数据,从第一个位置起,在已排序序列中从后向前扫描,找到相应位置并插入。
最优时间复杂度为:O(n)
最坏时间复杂度为:O(n^2)
稳定性:稳定
def insert_sort(a_list):
for i in range(1, len(a_list)):
for j in range(i, 0, -1): # 从后往前进行比较
if a_list[j] < a_list[j - 1]: # 若小于前面的,则进行交换
a_list[j], a_list[j - 1] = a_list[j - 1], a_list[j]
四、快速排序
原理:
一趟排序将数据分割成独立的两部分,一部分的所有数据都比另外的一部分所有数据都小,按此方法对这两部分递归快速排序,以此达到整个数据成为有序序列。
具体操作:应用两个游标i和j,找一个基准(一般选第一个数据,i=0),从右往左找小于基准的值j,将其放在i的位置,然后从左往右找大于基准的值i,将其放在j的位置。直到i>=j为止,将基准赋给第i个位置,然后对基准的左右两部分进行快速排序递归,直到整个序列有序。
最优时间复杂度为:O(nlogn)
最坏时间复杂度为:O(n^2)
空间复杂度:O(nlogn)
稳定性:不稳定
def quick_sort(a_list, start, end):
if start >= end: # 递归结束标志
return
mid = a_list[start] # 将开始数据作为基准
low = start
high = end
while low < high:
while low < high and a_list[high] >= mid: # 大于基准前移
high -= 1
a_list[low] = a_list[high] # 小于基准元素放在左指针位置
while low < high and a_list[low] < mid: # 小于基准后移
low += 1
a_list[high] = a_list[low] # 大于基准元素放在右指针位置
a_list[low] = mid # 基准放在中间位置
quick_sort(a_list, start, low - 1) # 递归左
quick_sort(a_list, low + 1, end) # 递归右
五、希尔排序
原理:
把数组按下标的一定增量分组,对每组使用直接插入算法排序,逐渐减少增量至1时,整个数组完成排序。
最优时间复杂度为:根据增量不同而不同
最坏时间复杂度为:O(n^2)
稳定性:不稳定
def shell_sort(alist):
n = len(alist)
# 初始步长
gap = n // 2
while gap > 0:
# 按步长进行插入排序
for i in range(gap, n):
j = i
# 插入排序
while j >= gap and alist[j - gap] > alist[j]:
alist[j - gap], alist[j] = alist[j], alist[j - gap]
j -= gap
# 得到新的步长
gap = gap // 2
六、归并排序
原理:
比较两个数组最前面的数,谁小先取谁,取后相应的指针往后移一位,再比较,直到一个数组为空,最后把另一个数组余下的复制过来即可。
最优时间复杂度为:O(nlogn)
最坏时间复杂度为:O(nlogn)
空间复杂度:O(n)
稳定性:稳定
def merge_sort(a_list):
if len(a_list) <= 1: # 递归结束条件
return a_list
# 二分分解
num = len(a_list) // 2
left = merge_sort(a_list[:num]) # 左递归
right = merge_sort(a_list[num:]) # 右递归
return merge(left, right)
# 按从小到大合并两个列表
def merge(left, right):
l, r = 0, 0
ans = []
while l < len(left) and r < len(right):
if left[l] < right[r]:
ans.append(left[l])
l += 1
else:
ans.append(right[r])
r += 1
ans += left[l:]
ans += right[r:]
return ans