《算法导论》排序算法的分析和实现
来源:互联网 发布:linux操作系统原理pdf 编辑:程序博客网 时间:2024/05/21 11:25
排序算法分析
常见(内)排序算法分类:
- 比较排序
- 插入排序/选择排序/冒泡排序
- 归并排序
- 快速排序
- 堆排序
- Shell排序
- 非比较排序
- 计数排序
- 基数排序
- 桶排序
排序的稳定性&复杂度分析
稳定性是针对相同大小的元素,如果排序算法不改变相同大小的元素原来的顺序,则算法是稳定的。也就是说,稳定的排序算法会让原本有相同键值的记录维持相同的次序。
时间复杂度:执行时间取决于比较次数和交换次数
空间复杂度:取决于消耗的额外内存空间(auxiliary space,相对于in-place来讲)
- 使用堆栈、记录表
- 使用链表(指针)、数组(索引)来访问元素
- 排序元素的副本
稳定排序:
- 插入/冒泡,
- 归并,
- 原地归并,
- 计数,
- 桶,
- 二叉树排序,期望
不稳定排序:
- 选择,
- Shell排序
- 堆排序,
- 快排,期望
说明:如无例外,以下的算法说明和实现均以升序排序为例。
插入排序
插入排序可以类比打牌:每次摸到一张牌,会和左手上排好序的牌,从后往前逐一比较大小,直到找到合适的位置(第一个大于的元素之后)插好。
主要步骤
- 循环遍历元素A[i]
- 将A[i]和其之前的元素A[j]循环逐一比较大小,只要当前A[j]大于A[i],A[j]就往后移动一位(覆盖下一个值),并继续比较A[i]和再前一个A[j]的大下
- 直到第一个A[j]小于A[i],跳出循环
- 此时A[j]的后一位,就是A[i]要放的位置
""" Insert Sort @author: Shangru @date: 2015/03/11"""def insert_sort(A): for i in range(len(A)): cur = A[i] j = i - 1 # j之前的已排好 while j >= 0 and A[j] > cur: # 当前数小于排好的,排好的后移一位,空出位置 A[j + 1] = A[j] j = j - 1 A[j + 1] = cur return A
复杂度分析
对于小规模数据,插入排序是快速的原地排序算法。
- 时间:最好
O(n) ,最坏O(n2) ,平均O(n2) - 空间:
O(1) - 稳定排序
选择排序
选择排序也可以类比为打扑克牌:每次从未排序的牌里选择抽出最小的那张牌,插到左边已排好的牌末尾。
主要步骤
- 循环遍历每一个元素A[i]
- 在每次循环内,逐个访问未排序的元素A[j],比较A[i]和未排序元素A[j]的大小
- 如果当前元素A[i]大于A[j],则交换两个元素。比较完这一趟后,未排序元素中的最小值会放到A[i],成为已排好的元素。
- 继续访问下一个A[i],对比未排序的A[j]
def select_sort(A): n = len(A) for i in range(n - 1): for j in range(i + 1, n): if A[j] < A[i]: A[i], A[j] = A[j], A[i] return A
复杂度分析
- 时间:最好
O(n2) ,最坏O(n2) ,平均O(n2) - 空间:
O(1) - 不稳定排序
冒泡排序
主要原理
每次比较相邻两个元素,如果存在逆序则互换两个元素的位置,使得小数在前,大数在后。类似于冒泡,每次遍历完后,较大的元素都会往后移(“沉”)。重复n次可以让数组有序。
主要步骤
def bubble_sort(A): n = len(A) for i in xrange(n): for j in xrange(n - 1, i, -1): if A[j] < A[j - 1]: A[j], A[j - 1] = A[j - 1], A[j] return Aprint bubble_sort([9, 8, 7, 6, 5, 4])
复杂度分析
- 时间:最好
O(n) ,最坏O(n2) ,平均O(n2) - 空间:
O(1) - 稳定排序
对于原始的算法,有2种优化方法:
1. 如果某一趟遍历,没有发生数据交换,则不需要再循环访问。设flag标识,跳出循环。
2. 记录某次遍历时最后发生数据交换的位置,这个位置之后的数据有序,不用再排序。记录这个位置,可以确定下次循环的范围。
实现方法参考:https://github.com/wuchong/Algorithm-Interview/blob/master/Sort/python/BubbleSort.py
希尔排序
Shell排序(Donald Shell, 1959)本质上是分组插入排序,但却是非稳定排序。
主要步骤
将输入待排序元素分成多个组(相隔gap个数的元素组成),分别对各组进行插入排序后,按原来对应的方式拼接回来。然后依次减小gap,再进行排序。直到整个序列基本有序,gap足够小,为1时就变成插入排序,这可以保证数据一定会被排序。
参考Wikipedia(https://en.wikipedia.org/wiki/Shellsort)的例子:
假设有一序列[ 13 14 94 33 82 25 59 94 65 23 45 27 73 25 39 10 ]
,以步长(增量)为5,每隔5个取元素,得到5列分组:
13 14 94 33 8225 59 94 65 2345 27 73 25 3910
对每组(即每列)插入排序,得到
10 14 73 25 2313 27 94 33 3925 59 94 65 8245
将四行数字拼接一起,得到[ 10 14 73 25 23 13 27 94 33 39 25 59 94 65 82 45 ]
,然后递减步长,以3为步长排序:
10 14 7325 23 1327 94 3339 25 5994 65 8245
排序后得到:
10 14 1325 23 3327 25 5939 65 7345 94 8294
再以1为步长进行排序即可。
代码实现
def shell_sort(A): n = len(A) gap = int(round(n / 2)) # initial gap while gap > 0: for i in xrange(gap, n): temp = A[i] # insert sort j = i while j >= gap and A[j - gap] > temp: A[j] = A[j - gap] j -= gap A[j] = temp gap = int(round(gap / 2)) # update gap return A
复杂度分析
Shell排序的复杂度与gap大小有关:
- 当gap取n/2^i
,最差复杂度为
- 当gap取2^k - 1
,最差为
- 当gap取2^i*3^j
,最差为
归并排序
归并排序是分治法的经典使用:
1. 分解:把原问题(n个元素的排序)分解为两个子问题(n/2个元素的排序),
2. 递归求解:归并法(Merge)对两个子序列递归的排序
3. 合并:再回到上层,合并这两个子问题的结果(合并两个已排序的子序列)
优势:
1. 归并对于连续存储的数据结构有优势(顺序地merge),如链表(只需要
2. 归并可以不用递归实现。
3. 稳定排序
劣势:
1. 和堆排序
""" Merge Sort @author: Shangru """def MergeSort(A, l, r): """ Sort A[l..r], divide into sorting A[l..mid], A[mid+1..r] """ if l < r: mid = (l + r) / 2 MergeSort(A, l, mid) MergeSort(A, mid + 1, r) Merge(A, l, mid, r) return Adef Merge(A, l, mid, r): """ Merge two sorted arrays into one A[l..mid], A[mid+1..r] -> A[l..r] Need extra space left & right """ leftlen = mid - l + 1 rightlen = r - mid left = A[l : mid + 1] right = A[mid + 1: r + 1] print left, right i, j, k = 0, 0, l while i < leftlen and j < rightlen: if left[i] <= right[j]: A[k] = left[i] i += 1 else: A[k] = right[j] j += 1 k += 1 if i == leftlen: A[k : r + 1] = right[j : rightlen] else: A[k : r + 1] = left[i : leftlen] return Aif __name__ == '__main__': print MergeSort([9,8,7,6,5,4,3,2,1], 0, 8)
复杂度分析
- 时间:最优
O(nlogn) ,最差O(nlogn) ,平均O(nlogn) - 空间:最差
O(n)
快速排序
快排主要是划分函数partition加分治法,通过划分函数把原序列划分成两个子序列,一个全部比基准数小,另一个全部比基准数大,分别对两个子序列递归调用自身快排。
优势:
1. 实际应用中比其他
2. 原地排序,额外空间复杂度
劣势:
1. 递归算法
2. 不是稳定算法
""" Quick Sort @date: 2015/03/11""" def quick_sort(A, l, r): if l < r: pivot = partition(A, l, r) quick_sort(A, l, pivot - 1) quick_sort(A, pivot + 1, r) else: return Adef partition(A, l, r): x = A[r] pivot = l - 1 for i in xrange(l, r + 1): if A[i] < x: pivot += 1 A[pivot], A[i] = A[i], A[pivot] A[pivot + 1], A[r] = A[r], A[pivot + 1] return pivot + 1
划分函数partition的时间复杂的为
import randomdef random_quick_sort(A, l, r): if l < r: pivot = random_partition(A, l, r) random_quick_sort(A, l, pivot - 1) random_quick_sort(A, pivot + 1, r) else: returndef random_partition(A, l, r): rand = random.randint(l, r) A[rand], A[r] = A[r], A[rand] pivot = l - 1 for i in xrange(l, r + 1): if A[i] < A[r]: pivot += 1 A[pivot], A[i] = A[i], A[pivot] A[pivot + 1], A[r] = A[r], A[pivot + 1] return pivot + 1
复杂度分析
- 时间:最优
O(nlogn) ,最差O(n2) ,平均O(nlogn) - 空间:
O(n) ,O(logn) (Sedgewick 1978)
- 《算法导论》排序算法的分析和实现
- 《算法导论的Java实现》 堆排序
- 《算法导论》的桶排序C++实现
- 基本排序算法的分析和实现
- 常用排序算法的实现和分析
- 八大排序算法的实现和分析
- 【算法导论】 插入排序实现
- 【算法导论】插入排序实现
- 【算法导论】归并排序实现
- 【算法导论】堆排序实现
- 【算法导论】快速排序实现
- 算法导论堆排序实现
- 算法导论快速排序实现
- 算法导论计数排序实现
- 算法导论插入排序算法python实现
- 算法导论合并排序算法python实现
- 算法导论排序算法简单实现
- 《算法导论》排序算法
- 离人
- Java学习笔记 day02
- lintcode将二叉树拆成链表
- Adobe illustrator cs6安装并破解流程
- 一张Ubuntu的窗口切换图
- 《算法导论》排序算法的分析和实现
- C++封装
- Java多线程处理大量数据
- Shiro异常 : IllegalArgumentException: Line argument must contain a key and a value. Only one
- JVM : 垃圾回收
- 关于页面的input checkbox
- ConstraintLayout
- 购物商城---购物车,结算
- 链式向前星