来自剑指offer:面试官经常要求应聘者比较插入排序、冒泡排序,归并排序、快速排序等不同算法的优劣,要求能够从额外空间开销、平均时间复杂度和最差时间复杂度去比较它们的优缺点。快排是重点。
概述
1.1 排序的稳定性
令狐冲的成绩和张无忌的成绩一样,未排序时令狐冲在前,排序后令狐冲依然在前,这样就是稳定的排序。
如果二者颠倒,那就是不稳定的排序。

1.2 内外排序
- 内排序是指在排序的整个过程中,待排序记录全部放置在内存中。
- 外排序是排序记录个数太多,不能同时放在内存,整个排序需要内外存之间多次交换数据才能实现
我们主要学习的是内部排序,常见的八大排序算法,如下图所示:

1.3 性能比较

1、冒泡排序
1.1 算法描述
两两比较相邻记录的关键字,如果反序则交换,直到没有反序的记录为止
1.2 演示

1.3 代码实现
# -*- coding: utf-8 -*-
class Solution(object):
def bubble_sort(self,nums):
arr_len = len(nums)
for i in range(arr_len):
for j in range(arr_len - 2,i-1,-1):
if nums[j] > nums[j+1]:
nums[j],nums[j+1] = nums[j+1],nums[j]
print(nums)
return nums
if __name__ == "__main__":
s = Solution()
nums = list(map(int,input().split()))
print(s.bubble_sort(nums))
2、选择排序
2.1 算法描述
选择排序就是在未排序的序列中找到最小(大)元素,存放到排序序列起始的位置,再从剩余未排序的元素中继续寻找最小(大)元素,然后放到已排序序列的末尾。以此类推,直到所有元素均排序完毕。性能略优于冒泡排序
2.2 演示
2.3 代码实现
# -*- coding: utf-8 -*-
class Solution(object):
def select_sort(self,nums):
arr_len = len(nums)
for i in range(arr_len):
min_idx = i
for j in range(i+1,arr_len):
if nums[j] < nums[min_idx]:
nums[min_idx],nums[j] = nums[j],nums[min_idx]
print(nums)
return nums
if __name__ == "__main__":
s = Solution()
nums = list(map(int,input().split()))
print(s.select_sort(nums))
3、插入排序
3.1 算法描述
类似打扑克
将第一待排序序列的第一个元素看做一个有序序列,把第二个元素到最后一个元素当成是未排序的序列。
从头到尾依次扫描未排序的序列,将扫描到的每个元素插入有序序列的适当位置。
3.2 演示
3.3 代码实现
# -*- coding: utf-8 -*-
class Solution(object):
def insert_sort(self,nums):
arr_len = len(nums)
for i in range(1,arr_len):
key = nums[i]
j = i - 1
while j >= 0 and key < nums[j]:
nums[j+1] = nums[j]
j -= 1
nums[j+1] = key
return nums
if __name__ == "__main__":
s = Solution()
nums = list(map(int,input().split()))
print(s.insert_sort(nums))
4、希尔排序
4.1 算法描述
希尔排序也称递减增量排序算法,是插入排序的一种更高效的改进版本,是非稳定算法。
基本思想是:先将整个待排序的记录序列分割成为若干子序列分别进行直接插入排序,待整个序列中的记录"基本有序"时,再对全体记录进行依次直接插入排序。
时间复杂度:n^1.5
4.2 演示
第一遍:gap = 5
4.3 代码实现
# -*- coding: utf-8 -*-
class Solution(object):
def shell_sort(self,nums):
arr_len = len(nums)
gap = int(arr_len / 2)
while gap > 0:
for i in range(gap,arr_len):
temp = nums[i]
j = i
while j >= gap and nums[j-gap] > temp:
nums[j] = nums[j-gap]
j -= gap
nums[j] = temp
gap = int(gap/2)
return nums
if __name__ == "__main__":
s = Solution()
nums = list(map(int,input().split()))
print(s.shell_sort(nums))
5、归并排序
5.1 算法描述
采用分治法。
分割:递归地把当前序列平均分割成两半。
集成:在保持元素顺序的同时将上一步得到的子序列集成到一起(归并)。
- 时间复杂度:O(Nlog(N)) 空间复杂度:O(N+logN)
- 归并排序是一种比较占内存,但是效率高且稳定的算法
5.2 演示
5.3 代码实现
def merge(arr, l, m, r):
n1 = m - l + 1
n2 = r- m
# 创建临时数组
L = [0] * (n1)
R = [0] * (n2)
# 拷贝数据到临时数组 arrays L[] 和 R[]
for i in range(0 , n1):
L[i] = arr[l + i]
for j in range(0 , n2):
R[j] = arr[m + 1 + j]
# 归并临时数组到 arr[l..r]
i = 0 # 初始化第一个子数组的索引
j = 0 # 初始化第二个子数组的索引
k = l # 初始归并子数组的索引
while i < n1 and j < n2 :
if L[i] <= R[j]:
arr[k] = L[i]
i += 1
else:
arr[k] = R[j]
j += 1
k += 1
# 拷贝 L[] 的保留元素
while i < n1:
arr[k] = L[i]
i += 1
k += 1
# 拷贝 R[] 的保留元素
while j < n2:
arr[k] = R[j]
j += 1
k += 1
def mergeSort(arr,l,r):
if l < r:
m = int((l+(r-1))/2)
mergeSort(arr, l, m)
mergeSort(arr, m+1, r)
merge(arr, l, m, r)
arr = [12, 11, 13, 5, 6, 7]
n = len(arr)
print ("给定的数组")
for i in range(n):
print ("%d" %arr[i]),
mergeSort(arr,0,n-1)
print ("\n\n排序后的数组")
for i in range(n):
print ("%d" %arr[i])
6、快速排序
6.1 算法描述
1、从数列中挑出一个元素,称为 “基准”(pivot);
2、重新排序数列,所有元素比基准值小的摆放在基准前面,所有元素比基准值大的摆在基准的后面(相同的数可以到任一边)。在这个分区退出之后,该基准就处于数列的中间位置。这个称为分区(partition)操作;3、递归地(recursive)把小于基准值元素的子数列和大于基准值元素的子数列排序;
6.2 演示
6.3 代码实现
def partition(arr,low,high):
i = ( low-1 ) # 最小元素索引
pivot = arr[high]
for j in range(low , high):
# 当前元素小于或等于 pivot
if arr[j] <= pivot:
i = i+1
arr[i],arr[j] = arr[j],arr[i]
arr[i+1],arr[high] = arr[high],arr[i+1]
return ( i+1 )
# arr[] --> 排序数组
# low --> 起始索引
# high --> 结束索引
# 快速排序函数
def quickSort(arr,low,high):
if low < high:
pi = partition(arr,low,high)
quickSort(arr, low, pi-1)
quickSort(arr, pi+1, high)
arr = [10, 7, 8, 9, 1, 5]
n = len(arr)
quickSort(arr,0,n-1)
print ("排序后的数组:")
for i in range(n):
print ("%d" %arr[i])
7、堆排序
7.1 算法描述
- 堆具有下列性质的完全二叉树:每个结点的值都大于或等于其左右孩子结点的值,称为大顶堆;每个结点小于或等于其左右孩子结点的值,称为小顶堆。
- 基本思想: 将待排序的序列构造一个大顶堆,此时,整个序列的最大值就是堆顶的根结点。将它移走(其实就是将其与堆数组的末尾元素交换,此时末尾元素就是最大值),然后将剩余的n-1个序列重新构造成一个堆,这样就会得到n个元素的次大值。如此反复执行,便能得到一个有序的序列。
- 解决的两个问题
1、如何由无序序列构成一个堆
2、在输出堆顶元素后,如何调整剩余元素称为一个新的堆
7.2 演示

7.3 代码实现
def heapify(arr, n, i):
largest = i
l = 2 * i + 1 # left = 2*i + 1
r = 2 * i + 2 # right = 2*i + 2
if l < n and arr[i] < arr[l]:
largest = l
if r < n and arr[largest] < arr[r]:
largest = r
if largest != i:
arr[i],arr[largest] = arr[largest],arr[i] # 交换
heapify(arr, n, largest)
def heapSort(arr):
n = len(arr)
# Build a maxheap.
for i in range(n, -1, -1):
heapify(arr, n, i)
# 一个个交换元素
for i in range(n-1, 0, -1):
arr[i], arr[0] = arr[0], arr[i] # 交换
heapify(arr, i, 0)
arr = [ 12, 11, 13, 5, 6, 7]
heapSort(arr)
n = len(arr)
print ("排序后")
for i in range(n):
print ("%d" %arr[i])
参考
大话数据结构
菜鸟教程
十大经典排序算法(动画演示)
数据结构-排序算法总结
数据结构常见的八大排序算法(详细整理)
欢迎关注我的公众号-owen炼丹之路
