基本概念和分类
- 所谓排序,就是使一串记录,按照其中的某个或某些关键字的大小,递增或递减的排列起来的操作。
- 排序算法,就是如何使得记录按照要求排列的方法。
排序算法的稳定性
- 在待排序的记录序列中,存在多个具有相同的关键字的记录,若经过排序,这些记录的相对次序保持不变,即在原序列中,r[i]=r[j],且r[i]在r[j]之前,而在排序后的序列中,r[i]仍在r[j]之前,则称这种排序算法是稳定的;否则称为不稳定的。
- 对于不稳定的排序算法,只要举出一个实例,即可说明它的不稳定性;
- 对于稳定的排序算法,必须对算法进行分析从而得到稳定的特性。
- 排序算法是否为稳定的是由具体算法决定的,不稳定的算法在某种条件下可以变为稳定的算法,而稳定的算法在某种条件下也可以变为不稳定的算法。
- 除非要排序的内容是一个复杂对象的多个数字属性,且原本的初始顺序存在意义,且要求排序后需要保持初始的顺序意义,那么我们才需要使用到稳定性的算法。否则使用稳定性算法依旧将毫无意义。
内排序和外排序
- 内排序:排序过程中,待排序的所有记录全部放在内存中
- 外排序:排序过程中,使用到了外部存储。
影响排序算法性能的因素
-
时间复杂度
- 即时间性能,高效率的排序算法应该是具有尽可能少的关键字比较次数和记录的移动次数
- 时间频度: 一个算法执行所耗费的时间,从理论上是不能算出来的,必须上机运行测试才能知道。但我们不可能也没有必要对每个算法都上机测试,只需知道哪个算法花费的时间多,哪个算法花费的时间少就可以了。并且一个算法花费的时间与算法中语句的执行次数成正比例,哪个算法中语句执行次数多,它花费时间就多。 一个算法中的语句执行次数称为语句频度或时间频度。记为T(n)。
- 在时间频度中,n称为问题的规模,当n不断变化时,时间频度T(n)也会不断变化。但有时我们想知道它变化时呈现什么规律。为此,我们引入时间复杂度概念。 一般情况下,算法中基本操作重复执行的次数是问题规模n的某个函数,用T(n)表示,若有某个辅助函数f(n),使得当n趋近于无穷大时,T(n)/f(n)的极限值为不等于零的常数,则称f(n)是T(n)的同数量级函数。记作T(n)=O(f(n)),称O(f(n))为算法的 渐进时间复杂度,简称 时间复杂度。
- 时间复杂度常用大O(x)符号表述 空间复杂度
- 主要是执行算法所需要的辅助空间,越少越好。 算法复杂性
-
主要是指代码的复杂性。
n:数据规模
k:“桶”的个数
In-place:占用常数内存,不占用额外内存
Out-place:占用额外内存
冒泡排序(Bubble Sort)
- 两两比较相邻记录的关键字,如果反序则交换,直到没有反序记录为止。
- 最简单排序实现:bubble_sort_simple
- 冒泡排序:bubble_sort
- 改进的冒泡排序:bubble_sort_advance
- 什么时候最快(Best Cases):当输入的数据已经是正序时。
- 什么时候最慢(Worst Cases):当输入的数据是反序时。
冒泡排序动图演示:
冒泡排序代码实现:
class BSList:
def __init__(self, lis=None):
self.r = lis
def swap(self, i, j):
"""定义一个交换元素的方法,方便后面调用。"""
self.r[i], self.r[j] = self.r[j], self.r[i]
def bubble_sort_simple(self):
"""最简单的交换排序,时间复杂度O(n^2)
"""
lis = self.r
length = len(lis)
for i in range(length):
for j in range(i+1, length):
if lis[i] > lis[j]:
self.swap(i, j)
def bubble_sort(self):
"""冒泡排序,时间复杂度O(n^2)
"""
lis = self.r
length = len(lis)
for i in range(length):
j = length-2
while j >= i:
if lis[j] > lis[j+1]:
self.swap(j, j+1)
j -= 1
def bubble_sort_advance(self):
"""冒泡排序改进算法,时间复杂度O(n^2)
设置flag,当一轮比较中未发生交换动作,则说明后面的元素其实已经有序排列了。
对于比较规整的元素集合,可提高一定的排序效率。
"""
lis = self.r
length = len(lis)
flag = True
i = 0
while i < length and flag:
flag = False
j = length - 2
while j >= i:
if lis[j] > lis[j + 1]:
self.swap(j, j + 1)
flag = True
j -= 1
i += 1
def __str__(self):
ret = ""
for i in self.r:
ret += " %s" % i
return ret
选择排序(Selection Sort)
- 选择排序不受输入数据的影响,即在任何情况下时间复杂度不变。选择排序每次选出最小的元素,因此需要遍历 n-1 次。
- 通俗的说就是,对尚未完成排序的所有元素,从头到尾比一遍,记录下最小的那个元素的下标,也就是该元素的位置。再把该元素交换到当前遍历的最前面。其效率之处在于,每一轮中比较了很多次,但只交换一次。因此虽然它的时间复杂度也是O(n^2),但比冒泡算法还是要好一点。
巧用:0~100的已经排序的序列,如何随机打乱它的顺序,当然也可以变成如何最快的生成0~100的随机数。一种比较好的方法,就是随机在数组里挑选元素,然后置换这个数据和最后一个元素的位置,接下来在不包含最后一个元素的数组里继续找随机数,然后继续后置。
选择排序动图演示:
选择排序代码实现:
class SSList:
def __init__(self, lis=None):
self.r = lis
def swap(self, i, j):
"""定义一个交换元素的方法,方便后面调用。"""
self.r[i], self.r[j] = self.r[j], self.r[i]
def select_sort_s(self):
"""
简单选择排序,时间复杂度O(n^2)
升序
"""
lis = self.r
length = len(self.r)
for i in range(length-1):
minimum = i
for j in range(i + 1, length):
if lis[minimum] > lis[j]:
minimum = j
if i != minimum:
self.swap(i, minimum)
def select_sort_j(self):
"""
简单选择排序,时间复杂度O(n^2)
降序
"""
lis = self.r
length = len(self.r)
for i in range(length-1):
maxmum = i
for j in range(i + 1, length):
if lis[maxmum] < lis[j]:
maxmum = j
if i != maxmum:
self.swap(i, maxmum)
def __str__(self):
ret = ""
for i in self.r:
ret += " %s" % i
return ret
插入排序(Insertion Sort)
- 插入排序如同打扑克一样,每次将后面的牌插到前面已经排好序的牌中。
- 插入排序有一种优化算法,叫做拆半插入。因为前面是局部排好的序列,因此可以用折半查找的方法将牌插入到正确的位置,而不是从后往前一一比对。
- 折半查找只是减少了比较次数,但是元素的移动次数不变,所以时间复杂度仍为 O(n^2) !
插入排序动图演示:
插入排序代码实现:
class ISList:
def __init__(self, lis=None):
self.r = lis
def swap(self, i, j):
"""定义一个交换元素的方法,方便后面调用。"""
self.r[i], self.r[j] = self.r[j], self.r[i]
def insert_sort(self):
for i in range(len(self.r) - 1):
curNum, preIndex = self.r[i+1], i # curNum 保存当前待插入的数
while preIndex >= 0 and curNum < self.r[preIndex]: # 将比 curNum 大的元素向后移动
self.r[preIndex + 1] = self.r[preIndex]
preIndex -= 1
self.r[preIndex + 1] = curNum # 待插入的数的正确位置
def __str__(self):
ret = ""
for i in self.r:
ret += " %s" % i
return ret
希尔排序(Shell Sort)
- 希尔排序是插入排序的改进版本,其核心思想是用增量序列将原数据集合分割成若干个子序列,然后再对子序列分别进行直接插入排序,使子序列基本有序,最后再对全体记录进行一次直接插入排序。
- 将相距某个“增量”的记录组成一个子序列,这样才能保证在子序列内分别进行直接插入排序后得到的结果是基本有序而不是局部有序。
- 希尔排序的核心在于增量序列的设定。既可以提前设定好增量序列,也可以动态的定义增量序列。
- 增量序列中除1 外没有公因子元素,且最后一个增量元素必须为1。
希尔排序动图演示:
希尔排序代码实现:
class SSList:
def __init__(self, lis=None):
self.r = lis
def shell_sort(self):
lens = len(self.r)
gap = 1
while gap < lens // 3:
gap = gap * 3 + 1 # 动态定义间隔序列
while gap > 0:
for i in range(gap, lens):
curNum, preIndex = self.r[i], i - gap # curNum 保存当前待插入的数
while preIndex >= 0 and curNum < self.r[preIndex]:
self.r[preIndex + gap] = self.r[preIndex] # 将比 curNum 大的元素向后移动
preIndex -= gap
self.r[preIndex + gap] = curNum # 待插入的数的正确位置
gap //= 3 # 下一个动态间隔
def __str__(self):
ret = ""
for i in self.r:
ret += " %s" % i
return ret
归并排序(Merge Sort)
- 归并排序(Merging Sort):建立在归并操作上的一种有效的排序算法,该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。
- 将有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列有序合并。
- 若将两个有序的子序列合并成一个有序的序列,称为二路归并。
归并排序动图演示:
归并排序代码实现:
1、递归法
class MSList:
def __init__(self, lis=None):
self.r = lis
def merge_sort(self):
self.msort(self.r, self.r, 0, len(self.r) - 1)
def msort(self, list_sr, list_tr, s, t):
"""递归法"""
temp = [None for _ in range(len(list_sr))]
if s == t:
list_tr[s] = list_sr[s]
else:
m = int((s + t) / 2)
self.msort(list_sr, temp, s, m)
self.msort(list_sr, temp, m + 1, t)
self.merge(temp, list_tr, s, m, t)
def merge(self, list_sr, list_tr, i, m, n):
j = m + 1
k = i
while i <= m and j <= n:
if list_sr[i] < list_sr[j]:
list_tr[k] = list_sr[i]
i += 1
else:
list_tr[k] = list_sr[j]
j += 1
k += 1
if i <= m:
for l in range(0, m - i + 1):
list_tr[k + l] = list_sr[i + l]
if j <= n:
for l in range(0, n - j + 1):
list_tr[k + l] = list_sr[j + l]
def __str__(self):
ret = ""
for i in self.r:
ret += " %s" % i
return ret
if __name__ == '__main__':
slist = MSList([4, 1, 7, 3, 8, 5, 9, 2, 6, 0, 12, 77, 34, 23, 99])
slist.merge_sort()
print(slist)
2、迭代法
class MS_List:
def __init__(self, lis=None):
self.r = lis
def merge(self, l_from, l_to, low, mid, high):
"""
两段需要归并的序列从左往右遍历,逐一比较,小的就放到
l_to里去,l_from下标+1,l_to下标+1,然后再取,再比,再放,
最后l_from里的两段比完了,lto里留下的就是从小到大排好的一段。
:param l_from: 原来的列表
:param l_to: 缓存的列表
:param low: 左边一段的开头下标
:param mid: 左右两段的中间相隔的下标
:param high: 右边一段的最右下标
:return:
"""
i, j, k = low, mid, low
while i < mid and j < high:
if l_from[i] <= l_from[j]:
l_to[k] = l_from[i]
i += 1
else:
l_to[k] = l_from[j]
j += 1
k += 1
while i < mid:
l_to[k] = l_from[i]
i += 1
k += 1
while j < high:
l_to[k] = l_from[j]
j += 1
k += 1
def merge_pass(self, l_from, l_to, l_len, s_len):
"""
用来处理所有需要合并的段,这需要每段的长度,以及列表的总长。
最后的if语句处理表最后部分不规则的情况。
:param l_from: 原来的列表
:param l_to: 缓存的列表
:param l_len: 列表总长
:param s_len: 每段的长度
:return:
"""
i = 0
while i+2*s_len < l_len:
self.merge(l_from, l_to, i, i+s_len, i+2*s_len)
i += 2*s_len
if i+s_len < l_len:
self.merge(l_from, l_to, i, i+s_len, l_len)
else:
for j in range(i, l_len):
l_to[j] = l_from[j]
def merge_sort(self):
"""
主函数。
先安排一个同样大小的列表,作为辅助空间。
然后在两个列表直接做往复的归并,每归并一次s_len的长度增加一倍,
逐渐向l_len靠拢,当s_len==l_len时说明归并结束了。
归并完成后最终结果可能恰好保存在templist里,因此代码里做两次归并,
保证最后的结果体现在原始的lst列表里。
:param lst: 要排序的原始列表
:return:
"""
s_len, l_len = 1, len(self.r)
templist = [None]*l_len
while s_len < l_len:
self.merge_pass(self.r, templist, l_len, s_len)
s_len *= 2
self.merge_pass(templist, self.r, l_len, s_len)
s_len *= 2
def __str__(self):
ret = ""
for i in self.r:
ret += " %s" % i
return ret
if __name__ == '__main__':
s_list = MS_List([4, 1, 7, 3, 8, 5, 9, 2, 6, 0, 12, 77, 34, 23, 99])
s_list.merge_sort()
print(s_list)
快速排序(Quick Sort)
- 本质上来看,快速排序应该算是在冒泡排序基础上的递归分治法。它是处理大数据最快的排序算法之一。
- 它的主要缺点是非常脆弱,在实现时要非常小心才能避免低劣的性能。
- 快速排序算法的核心思想:通过一趟排序将待排记录分割成独立的两部分,其中一部分记录的关键字均比另一部分记录的关键字小,然后分别对这两部分继续进行排序,以达到整个记录集合的排序目的。
快速排序动图演示:
快速排序代码实现:
class QSList:
def __init__(self, lis=None):
self.r = lis
def swap(self, i, j):
"""定义一个交换元素的方法,方便后面调用。"""
self.r[i], self.r[j] = self.r[j], self.r[i]
def quick_sort(self):
"""调用入口"""
self.qsort(0, len(self.r) - 1)
def qsort(self, low, high):
"""递归调用"""
if low < high:
pivot = self.partition(low, high)
self.qsort(low, pivot - 1)
self.qsort(pivot + 1, high)
def partition(self, low, high):
"""
快速排序的核心代码。
其实就是将选取的pivot_key不断交换,将比它小的换到左边,将比它大的换到右边。
它自己也在交换中不断变换自己的位置,直到完成所有的交换为止。
但在函数调用的过程中,pivot_key的值始终不变。
:param low:左边界下标
:param high:右边界下标
:return:分完左右区后pivot_key所在位置的下标
"""
lis = self.r
pivot_key = lis[low]
while low < high:
while low < high and lis[high] >= pivot_key:
high -= 1
self.swap(low, high)
while low < high and lis[low] <= pivot_key:
low += 1
self.swap(low, high)
return low
def __str__(self):
ret = ""
for i in self.r:
ret += " %s" % i
return ret
if __name__ == '__main__':
Lis = [4, 1, 7, 3, 8, 5, 9, 2, 6]
islist = QSList(Lis)
islist.quick_sort()
print(islist)
另一个版本:
def quick_sort(lis):
# 封装一层的目的是方便用户调用
def qsort(lst, begin, end):
if begin >= end:
return
i = begin
key = lst[begin]
for j in range(begin+1, end+1):
if lst[j] < key:
i += 1
lst[i], lst[j] = lst[j], lst[i]
lst[begin], lst[i] = lst[i], lst[begin]
qsort(lst, begin, i-1)
qsort(lst, i+1, end)
qsort(lis, 0, len(lis)-1)
优化快速排序:
1. 优化选取的pivot_key
每次选取的pivot_key都是子序列的第一个元素,也就是lis[low],这就比较看运气。运气好时,该值处于整个序列的靠近中间值,则构造的树比较平衡,运气比较差,处于最大或最小位置附近则构造的树接近斜树。
为了保证pivot_key选取的尽可能适中,采取选取序列左中右三个特殊位置的值中,处于中间值的那个数为pivot_key,通常会比直接用lis[low]要好一点。
2. 减少不必要的交换
可以将pivot_key暂存在某个临时变量中,减少交换。
3. 优化小数组时的排序方法
快速排序算法的递归操作在进行大量数据排序时,其开销能被接受,速度较快。但进行小数组排序时则不如直接插入排序来得快,因此,根据数据的多少,判断使用哪种算法。
堆排序(Heap Sort)
- 堆排序可以说是一种利用堆的概念来排序的选择排序。分为两种方法:
- 大根堆:每个节点的值都大于或等于其子节点的值,用于升序排列;
- 小根堆:每个节点的值都小于或等于其子节点的值,用于降序排列。
- 其核心思想是:将待排序的序列构造成一个堆。此时,整个序列的最大(小)值就是堆的根节点。将它与堆数组的末尾元素交换,然后将剩余的n-1个序列重新构造成一个堆。反复执行前面的操作,最后获得一个有序序列。
- 实现堆排序需解决两个问题:
- 如何将n 个待排序的数建成堆;
- 输出堆顶元素后,怎样调整剩余n-1 个元素,使其成为一个新堆。
- 建堆方法:对初始序列建堆的过程,就是一个反复进行筛选的过程。
- 1)n 个结点的完全二叉树,则最后一个结点是第 (n/2) 个结点的子树。
- 2)筛选从第 (n/2) 个结点为根的子树开始,该子树成为堆。
- 3)之后向前依次对各结点为根的子树进行筛选,使之成为堆,直到根结点。
- 输出堆顶元素后,对剩余n-1元素重新建成堆的调整过程。
- 1)设有 n 个元素的堆,输出堆顶元素后,剩下 n-1 个元素。将堆底元素送入堆顶((最后一个元素与堆顶进行交换),堆被破坏,其原因仅是根结点不满足堆的性质。
- 2)将根结点与左、右子树中较大(小)元素的进行交换。
- 3)若与左子树交换:如果左子树堆被破坏,即左子树的根结点不满足堆的性质,则重复 2)。
- 4)若与右子树交换:如果右子树堆被破坏,即右子树的根结点不满足堆的性质。则重复方法 2)。
- 5)继续对不满足堆性质的子树进行上述交换操作,直到叶子结点,堆被建成。
- 称这个自根结点到叶子结点的调整过程为筛选。
堆排序动图演示(大根堆):
堆排序代码实现:
class HSList:
def __init__(self, lis=None):
self.r = lis
def swap(self, i, j):
"""定义一个交换元素的方法,方便后面调用。"""
self.r[i], self.r[j] = self.r[j], self.r[i]
def heap_sort(self):
length = len(self.r)
i = int(length / 2)
# 将原始序列构造成一个大根堆
# 遍历从中间开始,到0结束,其实这些是堆的分支节点。
while i >= 0:
self.heap_adjust(i, length - 1)
i -= 1
# 逆序遍历整个序列,不断取出根节点的值,完成实际的排序。
j = length - 1
while j > 0:
# 将当前根节点,也就是列表最开头,下标为0的值,交换到最后面j处
self.swap(0, j)
# 将发生变化的序列重新构造成大顶堆
self.heap_adjust(0, j - 1)
j -= 1
def heap_adjust(self, s, m):
"""核心的大根堆构造方法,维持序列的堆结构。"""
lis = self.r
temp = lis[s]
i = 2 * s
while i <= m:
if i < m and lis[i] < lis[i + 1]:
i += 1
if temp >= lis[i]:
break
lis[s] = lis[i]
s = i
i *= 2
lis[s] = temp
def __str__(self):
ret = ""
for i in self.r:
ret += " %s" % i
return ret
if __name__ == '__main__':
slist = HSList([4, 1, 7, 3, 8, 5, 9, 2, 6, 0, 12, 77, 34, 23, 99])
slist.heap_sort()
print(slist)
计数排序(Counting Sort)
- 计数排序是在输入数据的范围 在M 到N 之间,则可以开辟一个大小为 N-M+1 的有序数组空间,将输入的数据值转化为键存储在该数组空间中,数组中键的值为键对应的数据出现的次数。
- 将数组空间的所有值初始化为0,遍历待排序数据,将每个数据对应数组空间的键的值自动加一。遍历完毕后,将数组空间有序输出。
- 是一种线性时间复杂度的排序。
计数排序动图演示:
计数排序代码实现:
class CSList:
def __init__(self, lis=None):
self.r = lis
def count_sort(self):
bucket = [0] * (max(self.r)-min(self.r) + 1) # 桶的个数
for num in self.r: # 将元素值作为键值存储在桶中,记录其出现的次数
bucket[num-min(self.r)] += 1
i = 0 # 索引
for j in range(len(bucket)):
while bucket[j] > 0:
self.r[i] = j
bucket[j] -= 1
i += 1
def __str__(self):
ret = ""
for i in self.r:
ret += " %s" % i
return ret
桶排序(Bucket Sort)
- 桶排序是计数排序的升级版。它利用了函数的映射关系,高效与否的关键就在于这个映射函数。
- 为了使桶排序更加高效,我们需要做到这两点:
- 在额外空间充足的情况下,尽量增大桶的数量
- 使用的映射函数能够将输入的 N 个数据均匀的分配到 K 个桶中
- 对于桶中元素的排序,选择何种排序算法对于性能的影响至关重要。
- 什么时候最快(Best Cases):当输入的数据可以均匀的分配到每一个桶中
- 什么时候最慢(Worst Cases):当输入的数据被分配到了一个桶中
桶排序动图演示:
桶排序代码实现:
class BSList:
def __init__(self, lis=None, defaultBucketSize=5):
self.r = lis
self.s = defaultBucketSize
def swap(self, i, j):
"""定义一个交换元素的方法,方便后面调用。"""
self.r[i], self.r[j] = self.r[j], self.r[i]
def insert_sort(self, buck):
for i in range(len(buck) - 1):
curNum, preIndex = buck[i+1], i # curNum 保存当前待插入的数
while preIndex >= 0 and curNum < buck[preIndex]: # 将比 curNum 大的元素向后移动
buck[preIndex + 1] = buck[preIndex]
preIndex -= 1
buck[preIndex + 1] = curNum # 待插入的数的正确位置
def bucket_Sort(self):
maxVal, minVal = max(self.r), min(self.r)
bucketSize = self.s # 如果没有指定桶的大小,则默认为5
bucketCount = (maxVal - minVal) // bucketSize + 1 # 数据分为 bucketCount 组
buckets = [] # 二维桶
for i in range(bucketCount):
buckets.append([])
# 利用函数映射将各个数据放入对应的桶中
for num in self.r:
buckets[(num - minVal) // bucketSize].append(num)
self.r.clear() # 清空 nums
# 对每一个二维桶中的元素进行排序
for bucket in buckets:
self.insert_sort(bucket) # 假设使用插入排序
self.r.extend(bucket) # 将排序好的桶依次放入到 nums 中
def __str__(self):
ret = ""
for i in self.r:
ret += " %s" % i
return ret
基数排序(Radix Sort)
- 基数排序是桶排序的一种推广,它所考虑的待排记录包含不止一个关键字。
- 基数排序有两种方法:
- MSD (主位优先法):从高位开始进行排序
- LSD (次位优先法):从低位开始进行排序
基数排序动图演示:
基数排序代码实现:
class RSList:
def __init__(self, lis=None):
self.r = lis
def radix_sort(self):
mod = 10
div = 1
most_bit = len(str(max(self.r))) # 最大数的位数决定了外循环多少次
buckets = [[] for _ in range(mod)] # 构造 mod 个空桶
while most_bit:
for num in self.r: # 将数据放入对应的桶中
buckets[num // div % mod].append(num)
i = 0 # 索引
for bucket in buckets: # 将数据收集起来
while bucket:
self.r[i] = bucket.pop(0) # 依次取出
i += 1
div *= 10
most_bit -= 1
def __str__(self):
ret = ""
for i in self.r:
ret += " %s" % i
return ret
补充:外部排序
外部排序是指大文件排序,即待排序的数据记录以文件的形式存储在外存储器上。由于文件中的记录很多、信息容量庞大,所以整个文件所占据的存储单元往往会超过了计算机的内存量,因此,无法将整个文件调入内存中进行排序。于是,在排序过程中需进行多次的内外存之间的交换。
外部排序基本上由两个相对独立的阶段组成。首先,按可用内存大小,将外存上含 N 个记录的文件分成若干长度为 L(\<\N) 的子文件,依次读入内存,利用内部排序算法进行排序。然后,将排序后的文件写入外存,通常将这些文件称为归并段(Run)或“顺串”;对这些归并段进行逐步归并,最终得到整个有序文件。
要提高外排的效率,关键要解决以下4个问题:
- 如何减少归并次数
- 如何有效安排内存中的输入、输出块,使得机器的并行处理能力被最大限度利用
- 如何有效生成归并段
- 如何将归并段进行有效归并
针对这四大问题,人们设计了多种解决方案,例如釆用多路归并取代简单的二路归并,就可以减少归并轮数;例如在内存中划分出2个输出块,而不是只用一个,就可以设计算法使得归并排序不会因为磁盘的写操作而暂停,达到归并和写周转盘同时并行的效果;例如通过一种“败者树”的数据结构,可以一次生成2倍于内存容量的归并段;例如利用哈夫曼树的贪心策略选择归并次序,可以耗费最少的磁盘读写时间等。
其他一些比较:
基数排序 vs 计数排序 vs 桶排序
这三种排序算法都利用了桶的概念,但对桶的使用方法上有明显差异:
- 基数排序:根据键值的每位数字来分配桶
- 计数排序:每个桶只存储单一键值
- 桶排序:每个桶存储一定范围的数值
哪些排序算法可以在未结束排序时找出第 k 大元素?
冒泡、选择、堆排序、快排(想想为什么?)