排序算法总结
一、文章内容简介
- 基本概念
- 稳定性、衡量标准(时空复杂度)
- 分类:内排序、外排序
- 重点掌握堆排序、快排、归并(手写三个排序)
- 插入排序
- 直接插入排序
- 折半插入排序
- 希尔排序
- 交换排序
- 简单选择排序
- 堆排序
- 归并排序
- 基数排序
- 外表排序
- 各种内排序算法比较与应用
各排序算法实现的时候只是简单的实现了算法的思想,没考虑特殊输入的处理(如空数组,只包含1个元素的数组,low>high,high超出数组长度等情况)。面试手撕代码时,记得考虑各种特殊情况。
二、 插入排序
- 直接插入排序
- 基本思想
- python实现
def insert_sort(arr): for i in range(1, len(arr)): key = arr[i] # if arr[i] > arr[i-1]: j = i - 1 while arr[j] > key and j >= 0: arr[j+1] = arr[j] j -= 1 arr[j+1] = key return arr if __name__ == '__main__': arr = [9,7,8,6,2,4,3,5,1] # arr = [] # arr = [1] # arr = [9,8,7,6,5,4,3,2,1] # arr = [1,2,3,4,5,6,7,8,9] # print(build_max_heap(arr, len(arr)-1)) print(insert_sort(arr))
- 折半插入排序
- 希尔排序
三、交换排序
-
冒泡排序
- 关键步骤:比较、交换
- python实现
def bubble_sort(arr): for i in range(len(arr)): flag = False j = len(arr) - 1 while j > i: if arr[j-1] > arr[j]: arr[j-1], arr[j] = arr[j], arr[j-1] flag = True j -= 1 if not flag: return arr return arr def bubble_sort2(arr): flag = False for i in range(len(arr)): for j in range(len(arr)-1-i): if arr[j] > arr[j+1]: arr[j], arr[j+1] = arr[j+1], arr[j] flag = True if not flag: return arr return arr if __name__ == '__main__': arr = [7,9,8,6,2,4,3,5,1] print(bubble_sort(arr))
- 空间效率:O(1)
- 时间效率:
- 比较次数:n(n-1)/2
- 移动次数:3n(n-1)/2
- 最好:O(n^2)
- 平均:O(n^2)
- 稳定性:稳定
-
快速排序
- 基于分治的思想:待排序表L[0···n]中任意去一个数作为基准pivot(通常选择L[0]),通过一趟排序将待排序表划分为独立的两个部分L[0···k-1]和L[k+1···n],使得L[0···k-1]的所有元素小于pivot,L[k+1···n]的所有元素大于pivot,pivot放在最终的位置L[k]上,这个过程成为一趟快速排序。然后分别递归对两个子表进行上述过程,直到每个部分内只有一个元素或空为止,即所有元素放在了最终位置上。
- python实现代码如下:
def quick_sort(arr, low, high): if not arr: return None if low < high: pivotpos = partition(arr, low, high) quick_sort(arr, low, pivotpos-1) quick_sort(arr, pivotpos+1, high) return arr def partition(arr, low, high): pivot = arr[low] while low < high: while low < high and arr[high] >= pivot: high -= 1 arr[low] = arr[high] print("arr1:", arr) while low < high and arr[low] <= pivot: low += 1 arr[high] = arr[low] print("arr2:", arr) arr[low] = pivot return low if __name__ == '__main__': arr = [7,9,8,6,2,4,3,5,1] print(quick_sort(arr, 0, len(arr)-1))
- 空间效率:
- 平均: O(ln)
- 最坏:O(n)
- 最好:O(ln)
- 时间效率:
- 最坏:O(n^2)
- 最好:O(n·ln)
- 平均:O(n·ln)
- 稳定性:不稳定。L(3,2,2)
- 快排是所有内排序算法中平均性能最优的排序算法
四、选择排序
选择排序得基本思想:每一趟(例如第i趟)在后面n-i+1(n=1,2…,n-1)个待排序元素中选取关键字最小得元素,作为有序子序列得第i个元素,直到n-1趟排序完,待排序元素只剩下一个,就不用选了。
- 简单选择排序
- 基本思想:假设排序表为:L[0…n],第i躺排序直接从L[i…n]中选择最小得元素与L[i]交换,每一趟排序可以确定一个元素得最终位置
- python实现
def sinple_select_sort(arr): for i in range(len(arr)-1): min = i for j in range(i+1, len(arr)): if arr[j] < arr[min]: min=j if min != i: arr[i], arr[min] = arr[min], arr[i] return arr if __name__ == '__main__': arr = [7,9,8,6,2,4,3,5,1] # arr = [1,2,3,4,5,5,6,7,8,9] print(sinple_select_sort(arr))
- 空间效率:O(1)
- 时间效率:
- 移动次数:最好0(表有序),最坏3(n-1)
- 比较次数:比较次数与表序列得初始状态无关,始终是n(n-1)/2
- 复杂度:O(n^2)
- 稳定性:不稳定(L[2,2,1],交换元素的时候,可能导致第i个元素与其含有相同关键字元素得相对位置发生变化)
- 堆排序
堆的定义:n个关键字序列L[1…n]称为堆,当且仅当该序列满足:
大根堆:L(i)>=L(2i)且L(i)>=L(2i+1)
小根堆:L(i)<=L(2i)且L(i)<=L(2i+1)- 基本思想:堆排序是一种树形选择排序方法,它的特点是:在排序过程中,将L[1…n]看成是一棵完全二叉树的顺序存储结构,利用完全二叉树中双亲节点和孩子节点之间的内在关系,在当前无序区中选择最大(小)关键元素。通常将L[0]设置为哨兵,节点从L[1]开始。
- python实现
def build_max_heap(arr, len): for i in range(len//2, 0, -1): adjust_down(arr, i, len) return arr def adjust_down(arr, k, length): temp = arr[k] i = k j = 2 * i while j <= length: if j <= length and j + 1 <= length: if arr[j] < arr[j+1]: j += 1 if arr[j] > temp: arr[i] = arr[j] i = j j = 2 * i else: break arr[i] = temp def heap_sort(arr, len): build_max_heap(arr, len) for i in range(len, 1, -1): arr[i], arr[1] = arr[1],arr[i] adjust_down(arr, 1, i-1) return arr if __name__ == '__main__': arr = [0,7,8,6,2,4,3,5,1] # arr = [] # arr = [1] # arr = [9,8,7,6,5,4,3,2,1] # arr = [1,2,3,4,5,6,7,8,9] # print(build_max_heap(arr, len(arr)-1)) len = len(arr) - 1 print(heap_sort(arr, len))
五、归并排序
-
二路归并排序
- 基本思想
- python实现
import copy def merge(arr, low, mid, high): i = low j = mid + 1 k = low backup = copy.deepcopy(arr) while i <= mid and j <= high: if backup[i] < backup[j]: arr[k] = backup[i] i += 1 else: arr[k] = backup[j] j += 1 k += 1 while i <= mid: arr[k] = backup[i] i += 1 k += 1 while j <= high: arr[k] = backup[j] j += 1 k += 1 return arr def merge_sort(arr, low, high): if low < high: mid = (low+high) // 2 merge_sort(arr, low, mid) merge_sort(arr, mid+1, high) merge(arr, low, mid, high) return arr if __name__ == '__main__': arr = [7,9,8,6,2,4,3,5,1] arr = [] arr = [1] arr = [9,8,7,6,5,4,3,2,1] # arr = [1,2,3,4,5,5,6,7,8,9] print(merge_sort(arr, 0, len(arr)-1))
六、基数排序
七、外部排序
八、各种内排序算法的比较及应用
- 比较
- 时间复杂度
- O(n):简单选择排序、直接插入排序、冒泡排序
- O(n·ln):堆排序、快排(平均O(n·ln)、最坏O(n^2))、归并(最好最坏平均都是)
- 空间复杂度
- O(1):简单选择排序、插入排序、冒泡排序、希尔排序、堆排序
- O(n·ln):快排(平均n·ln、最坏n)
- O(n):归并
- 稳定性
- 稳定:插入排序、冒泡排序、归并排序、基数排序
- 不稳定:简单选择排序、快排、希尔、堆
- 基于比较的排序:直接插入、冒泡、简单选择、希尔、快排、堆排、归并
- 分配式排序:基数排序、桶排序
- 时间复杂度
- 应用
- 选取排序方法需要考虑的因素
- 待排序的元素数目n
- 元素本身信息量的大小
- 关键字的结构及其分布情况
- 稳定性要求
- 语言工具的条件,存储结构及辅助空间的大小
- 排序算法总结
- 若n较小(n<=50),则可以采用直接插入排序或简单选择排序。由于直接插入排序所需的记录移动操作较简单选择排序多,因此当记录本身信息量较大时,用简单选择排序较好
- 若文件的初始状态基本有序,则使用直接插入排序或冒泡排序为宜
- 若n较大时,则应选择时间复杂度为O(n·ln)的排序算法。快排、堆排序、归并。快排被认为是目前基于比较的内排序中最好的方法,当关键字随机分布时,快排平均时间最短。堆排序的辅助空间少于快排,并且不会出现快排可能出现的最坏情况。但两者都是不稳定的,归并排序是稳定的
- 若n很大,记录的关键字位数较少且可以分解时,采用基数排序较好。
- 当记录本身信息量较大,为了避免耗费大量时间移动记录,可用链表作为存储结构。
- 选取排序方法需要考虑的因素