【算法导论】快速排序

既然敢叫“快速排序”,必然有其过人之处。事实上,它确实是最快的通用内部排序算法。它由Hoare于1962年提出,相对归并排序来说不仅速度快,并且不需要辅助空间。

对于包含n个数的输入数组来说,快速排序是一种最坏情况时间复杂度为O(n^2)的排序算法。虽然最坏情况时间复杂度差,但是快速排序通常是实际排序应用中最好的选择,因为它的平均性能非常好:它的期望时间复杂度O(nlgn),而且其中隐含的常数因子非常小。另外,它还能够原址排序,甚至在虚存环境中也能很好的工作。

1. 快速排序的描述

快速排序使用了分治思想,下面是对一个典型的子数组A[p..r]进行快速排序的三步分治过程:

分解:数组A[p..r]被划分为两个(可能为空)子数组A[p..q-1]和A[q+1..r],使得A[p..q-1]中的每一个元素都小于等于A[q],而A[q]也小于等于A[q+1..r]中的每一个元素。其中,计算下标q也是划分过程的一部分。

解决:通过递归调用快速排序,对子数组A[p..q-1]和A[q+1..r]进行排序。

合并:因为子数组都是原址排序的,所以不需要合并操作:数据A[p..r]已经有序。

下面的程序实现快速排序:

QUICKSORT(A, p, r)
1. if p < r
2.     q = PARTITION(A, p, r)
3.     QUICKSORT(A, p, q-1)
4.     QUICKSORT(A, q+1, r)
为了排序一个数组A的全部元素,初始调用为QUICKSORT(A, 1, A.length)。

数组的划分:

算法的关键部分是PARTITION过程,它实现了对子数组A[p..r]的原址重排。

PARTITION方案一

PARTITION(A, p, r)
1. x = A[r]
2. i = p - 1
3. for j = p to r-1
4.     if A[j] <= x
5.         i = i+1
6.         exchange A[i] with A[j]
7. exchange A[i+1] with A[r]
8. return i+1
PARTITION方案二

HOARE-PARTITION(A, p, r)
 1. x = A[p]
 2. i = p-1
 3. j = r+1
 4. while TRUE
 5.     repeat
 6.         j = j-1
 7.     until A[j] <= x
 8.     repeat
 9.         i = i+1
10.     until A[j] >= x
11.     if i < j
12.         exchange A[i] with A[j]
13.     else return j
2. 快速排序的随机化版本
快速排序的随机化版本只对PARTITION和QUICKSORT的代码改动非常少。在新的划分程序中,我们只是在真正进行划分前进行一次交换:
RANDOMIZED-PARTITION(A, p, r)
1. i = RANDOM(p, r)
2. exchange A[r] with A[i]
3. return PARTITION(A, p, r)

一种改进的RANDOMIZED-QUICKSORT的方法是在划分时,要从子数组中更细致地选择作为主元的元素(而不是简单的随机选择),常用的做法是三数取中法:从子数组中随机选出三个元素,取其中位数作为主元(算法从略)。

新的快速排序不再调用PARTITION,而是调用RANDOMIZED-PARTITION:

RANDOMIZED-QUICKSORT(A, p, r)
1. if p < r
2.     q = RANDOMIZED-PARTITION(A, p, r)
3.     RANDOMIZED-QUICKSORT(A, p, q-1)
4.     RANDOMIZED-QUICKSORT(A, q+1, r)
3. 选择问题

选择第k大的数,问题描述:

输入:一个包含n个(互异的)数的集合A和一个整数i, 1 <= i <= n。

输出:元素x属于A,且A中恰好有i-1个其他元素小于它。

4. 期望为线性时间的选择算法

一般选择问题看起来要比找最小值这样的简单问题更难,但令人惊奇的是,这两个问题的渐近运行时间却是相同的:O(n)。与快速排序一样,我们仍然对数组进行递归划分。但不同的是,快速排序会递归处理划分的两边,而RANDOMIZED-SELECT只处理划分的一边。

RANDOMIZED-SELECT利用了RANDOMIZED-PARTITION过程。与RANDOMIZED-QUICKSORT一样,因为它的部分行为是由随机数生成器的输出决定的,所以RANDOMIZE-SELECT也是一个随机算法。以下是RANDOMIZED-SELECT的伪代码,它返回数组A[p..r]中第i小的元素。

RANDOMIZED-SELECT(A, p, r, i)
1. if p == r
2.     return A[p]
3. q = RANDOMIZED-PARTITION(A, p, r)
4. k = q-p+1
5. if i == k
6.     return A[q]
7. else if i < k
8.     return RANDOMIZED-SELECT(A, p, q-1, i)
9. else return RANDOMIZED-SELECT(A, q+1, r, i-k)

5. 最坏情况为线性时间的选择算法

通过执行以下步骤,算法SELECT可以确定一个有n>1个不同元素的输入数组中第i小的元素。(如果n=1,则SELECT只返回它的唯一输入数值作为第i小的元素。)

(1). 将输入数组的n个元素划分为n/5组,每组5个元素,且至多只有一组由剩下的n mod 5个元素组成;

(2). 寻找这n/5组中每一组的中位数:首先对每组元素进行插入排序,然后确定每组有序元素的中位数;

(3). 对第2步中找出的n/5个中位数,递用SELECT以找出其中位数x(如果有偶数个中位数,为了方便,约定x为较小的中位数); 

(4). 利用修改过的PARTITION版本,按中位数的中位数x对输入数组进行划分。让k比划分的低区中的元素数目多1,因此x是第k小的元素,并且有n-k个元素在划分的高区;

(5). 如果i=k,则返回x。如果i<k,则在低区递归调用SELECT来找出第i小的元素。如果i>k,则在高区递归查找第i-k小的元素。

内容概要:本文档主要展示了C语言中关于字符串处理、指针操作以及动态内存分配的相关代码示例。首先介绍了如何实现键值对(“key=value”)字符串的解析,包括去除多余空格和根据键获取对应值的功能,并提供了相应的测试用例。接着演示了从给定字符串中分离出奇偶位置字符的方法,并将结果分别存储到两个不同的缓冲区中。此外,还探讨了常量(const)修饰符在变量和指针中的应用规则,解释了不同类型指针的区别及其使用场景。最后,详细讲解了如何动态分配二维字符数组,并实现了对这类数组的排序与释放操作。 适合人群:具有C语言基础的程序员或计算机科学相关专业的学生,尤其是那些希望深入理解字符串处理、指针操作以及动态内存管理机制的学习者。 使用场景及目标:①掌握如何高效地解析键值对字符串并去除其中的空白字符;②学会编写能够正确处理奇偶索引字符的函数;③理解const修饰符的作用范围及其对程序逻辑的影响;④熟悉动态分配二维字符数组的技术,并能对其进行有效的排序和清理。 阅读建议:由于本资源涉及较多底层概念和技术细节,建议读者先复习C语言基础知识,特别是指针和内存管理部分。在学习过程中,可以尝试动手编写类似的代码片段,以便更好地理解和掌握文中所介绍的各种技巧。同时,注意观察代码注释,它们对于理解复杂逻辑非常有帮助。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值