招聘笔试中经常会考到排序算法,在此做一个总结。
一、算法概念
1.排序算法的稳定性
假定在待排序的记录序列中,存在多个具有相同的关键字的记录,若经过排序,这些记录的相对次序保持不变,即在原序列中,r[i]=r[j],且r[i]在r[j]之前,而在排序后的序列中,r[i]仍在r[j]之前,则称这种排序算法是稳定的;否则称为不稳定的。
1.简单选择排序
一趟简单排序的操作为:通过n-i次关键字间的比较,从n-i+1个记录中选出关键字最小的记录,并和第i(1<=i<=n)个记录交换之。
- 时间复杂度:
- 空间复杂度:
- 稳定性:不稳定
#选择排序
def selection_sort(list1):
print('selection_sort:')
list2 = list1.copy()
n = len(list2)
for i in range(0, n - 1):
min_ = i
print(list2)
for j in range(i + 1, n):
if list2[j] < list2[min_]:
min_ = j
list2[i],list2[min_] = list2[min_],list2[i]
print(list2,'\n')
2.冒泡排序
它重复地走访过要排序的元素列,依次比较两个相邻的元素,如果他们的顺序(如从大到小、首字母从A到Z)错误就把他们交换过来。走访元素的工作是重复地进行直到没有相邻元素需要交换,也就是说该元素已经排序完成。
- 时间复杂度:
- 空间复杂度:
- 稳定性:稳定
#冒泡排序
def bubble_sort(list1):
print('bubble_sort:')
list2 = list1.copy()
n = len(list2)
for i in range(0, n - 1):
print(list2)
for j in range(0, n - i - 1 ):
if list2[j] > list2[j+1]:
list2[j],list2[j+1] = list2[j+1],list2[j]
print(list2,'\n')
3.插入排序
将序列的第一个元素当做已经排序好的序列,然后从后面的第二个元素开始,逐个元素进行插入,直到整个序列有序为止。第i趟操作为:在含有i-1个记录的有序子序列r[1...i-1]中插入一个记录r[i]后,变成含有i个记录的有序子序列r[1..i]。
- 时间复杂度:
- 空间复杂度:
- 稳定性:稳定
#插入排序
def insertion_sort(list1):
print('insertion_sort:')
list2 = list1.copy()
n = len(list2)
for i in range(1,n):
print(list2)
value = list2[i]
pos = i
while pos > 0 and value< list2[pos - 1]:
list2[pos] = list2[pos - 1]
pos -= 1
list2[pos] = value
print(list2)
4.希尔排序
希尔排序(有时称为“递减递增排序”缩小增量排序 Diminishing Increment Sort)通过将原始列表分解为多个较小的子列表来改进插入排序,每个子列表使用插入排序进行排序。 选择这些子列表的方式是希尔排序的关键。不是将列表拆分为连续项的子列表,希尔排序使用增量i(有时称为 gap
),通过选择 i 个项的所有项来创建子列表。
下图1中显示增量为3得到的子列表。个子列表可以通过插入排序进行排序。完成这些排序后,我们得到如图2所示的列表。虽然这个列表没有完全排序,但发生了很有趣的事情。 通过排序子列表,我们已将项目移动到更接近他们实际所属的位置。
图1-2
图3展示了使用增量为 1 的插入排序; 换句话说,标准插入排序。注意,通过执行之前的子列表排序,我们减少了将列表置于其最终顺序所需的移位操作的总数。对于这种情况,我们只需要四次移位完成该过程。
图3
- 时间复杂度: 当n在某个特定范围内,希尔排序所需的比较和移动次数约为,当时,可以减少到
- 空间复杂度:
- 稳定性:不稳定
#希尔排序
def shell_sort(list1, gap):
print('shell_sort')
list2 = list1.copy()
n = len(list2)
sub_n = n//gap
for i in range(gap):
# print(i, list2)
for j in range(1, sub_n):
pos = j
value = list2[pos * gap + i]
# print(value)
while pos > 0 and value < list2[(pos - 1) * gap + i]:
# print(list2)
# print(pos, value, list2[(pos - 1) * gap + i])
list2[pos*gap+i] = list2[(pos - 1) * gap + i]
pos -= 1
list2[pos*gap+i] = value
print('move:', list2)
insertion_sort(list2)
5.堆排序
6.归并排序
首先介绍一下分治法(Divide and Conquer)
很多有用的算法结构上是递归的,为了解决一个特定问题,算法一次或多次递归调用其自身以解决若干子问题。这些算法遵循分值的思想。分治法在每层递归时有三个步骤:
- 分解:分解原问题为若干子问题,这些子问题是原问题的规模最小的实例
- 解决:解决这些子问题,递归的求解这些子问题。当子问题规模足够小,就可以直接求解
- 合并:合并这些子问题的解成原问题的解
下面介绍归并排序如何利用分治法解决问题:
考虑我们排序这个数组:[10,23,51,18,4,31,13,5] ,我们递归地将数组进行分解
当数组被完全分隔成只有单个元素的数组时,我们需要把它们合并回去,每次两两合并成一个有序的序列。
python实现:
- 分解:将待排序的n个元素分成各包含n/2个元素的子序列
- 解决:使用归并排序递归排序两个子序列
- 合并:合并两个已经排序的子序列以产生已排序的答案
- 时间复杂度:
- 空间复杂度:
- 稳定性:稳定
#归并排序
def merging_sort(list1, ):
list2 = list1.copy()
length_list2 = len(list2)
if length_list2 <= 1:
return list2
else:
mid = int(length_list2/2)
left_list = merging_sort(list2[:mid])
right_list = merging_sort(list2[mid:])
merged_list = merge_sorted_list(left_list, right_list)
return merged_list
def merge_sorted_list(left_list, right_list):
left_n = len(left_list)
right_n = len(right_list)
mergedlist = list()
i = j = 0
while i < left_n and j < right_n:
if left_list[i] < right_list[j]:
mergedlist.append(left_list[i])
i += 1
else:
mergedlist.append(right_list[j])
j += 1
if i < left_n:
mergedlist.extend(left_list[i:])
if j < right_n:
mergedlist.extend(right_list[j:])
return mergedlist
7.快速排序
快排(与归并排序一样)也是一种分而治之(divide and conquer)的策略。归并排序把数组递归成只有单个元素的数组,之后再不断两两 合并,最后得到一个有序数组。这里的递归基本条件就是只包含一个元素的数组,当数组只包含一个元素的时候,我们可以认为它本来就是有序的(当然空数组也不用排序)。
快排的工作过程其实比较简单,三步走:
-
选择基准值 pivot 将数组分成两个子数组:小于基准值的元素和大于基准值的元素。这个过程称之为 partition
-
对这两个子数组进行快速排序。
-
合并结果
- 时间复杂度:
- 空间复杂度:
- 稳定性:不稳定
#快速排序
def quick_sort(list2, low, high):
print('quick_sort:')
# list2 = list1.copy()
if len(list2[low:high+1]) <= 1:
return list2
else:
list2, pivot = partition(list2, low, high)
print(list2)
quick_sort(list2, low, pivot-1 )
quick_sort(list2, pivot+1, high)
print(list2)
def partition(list2, low, high):
# list2 = list1.copy()
pivotkey = list2[low]
while low < high:
# print('low, high: {},{}'.format(low, high))
while low < high and pivotkey < list2[high]:
high -= 1
# print('high:',high)
list2[low] = list2[high]
while low < high and pivotkey > list2[low]:
low += 1
# print('low:', low)
list2[high] = list2[low]
list2[low] = pivotkey
return list2, low
8.基数排序
排序方法 | 平均时间复杂度 | 最好时间复杂度 | 最坏时间复杂度 | 辅助空间 | 稳定性 | 备注 |
冒泡排序 | O(n2) | O(n) | O(n2) | O(1) | 稳定 | n小时较好 |
简单选择排序 | O(n2) | O(n2) | O(n2) | O(1) | 不稳定 | n小时较好 |
直接插入排序 | O(n2) | O(n) | O(n2) | O(1) | 稳定 | 大部分已排序时较好 |
希尔排序 | O(nlogn)~O(n2) | O(n1.3) | O(n2) | O(1) | 不稳定 | s是所选分组 |
堆排序 | O(nlogn) | O(nlogn) | O(nlogn) | O(1) | 不稳定 | n大时较好 |
归并排序 | O(nlogn) | O(nlogn) | O(nlogn) | O(n) | 稳定 | n大时较好 |
快速排序 | O(nlogn) | O(n) | O(n2) | O(nlogn)~O(n) | 不稳定 | n大时较好 |
github代码:https://github.com/makang101/python-data-structure
参考:
problem-solving-with-algorithms-and-data-structure-using-python 中文版
数据结构(C语言版)严蔚敏