转载地址:http://www2.gliet.edu.cn/jpkc/datastructure/course/course_content.asp?chapter_id=9§ion_id=43
图9.3 冒泡排序的工作过程
冒泡排序算法比较容易理解和编程,但它不是一个高效率的算法。不考虑数组中记录的
排列情况,冒泡排序的比较次数总将是i,因此冒泡排序的时间复杂性为:
= O(n2)
一个记录比它前一记录的关键字小的概率有多大决定了记录交换的次数。我们可以假定
这个概率为平均情况下比较次数的一半,因此冒泡排序算法交换记录的时间复杂性也为
O(n2)。
9.3.2快速排序
快速排序是一种基于“分治法”的排序方法。快速排序首先选择一个“基准值”,然后
按照基准值调整其它记录的位置。假设数组中待排序的记录中有k个记录的关键字值小于基
准值,于是,这些记录放在数组的最左边的k个位置上,而大于基准值的记录被放在数组最
右边的n-k个位置上。这个过程称为一次划分。在给定划分中的点不必被排序,而基准值的
位置就是下标k。第一次划分之后,再用相同的算法对“基准值”左右子序列分别进行类似
的操作,其中一个子数组有k个记录,而另一个子数组有n-1-k个记录。即从每部分中选择
一个基准值,将其划分成更小的两部分。依此递归地做下去,直至每个子序列中的记录个数
均不超过一个为止,排序过程结束。
“基准值”的选择有多种方法。最简单的方法是使用第一个记录的关键字值。但是,如
果输入的数组是正序或者是逆序的,就会将所有的记录分到“基准值”的一边。较好的方法
是随机选取“基准值”,这样可以减少由于原始输入对排序造成的影响,但随机选取“基准
值”的开销较大。
下面我们以第一个记录的关键字值作为基准值来看快速排序的过程。为了实现一次划
分,我们可以从数组的两端移动下标,必要时交换记录,直到数组两端的下标相遇为止。为
此,附设两个指针i与j,通过j从当前序列的右端向左扫描,越过不小于基准值的记录。
当遇到小于基准值的记录时,扫描停止。通过i从当前序列的左端向右扫描,越过小于基准
值的记录。当遇到不小于基准值的记录时,扫描停止。交换两个方向扫描停止的记录a[i]与
a[j]。然后,继续扫描,直至i与j相遇为止。扫描和交换的过程结束。这时,i左边的记录
的关键字值都小于基准值,右边的记录的关键字值都不小于基准值。下标i即为基准值记录
的位置。图9.4给出了快速排序的一次划分。
46 30 82 90 56 17 95 15
i j
15 30 82 90 56 17 95 46
i j
15 30 46 90 56 17 95 82
i j
15 30 17 90 56 46 95 82
i j
15 30 17 46 56 90 95 82
i j
15 30 17 46 56 90 95 82
i j
图9.4 快速排序的一次划分过程。基准值为46
46 30 82 90 56 17 95 15
基准值=46
[15 30 17] 46 [56 90 95 82]
基准值=15 基准值=56
15 [30 17] 46 56 [90 95 82]
基准值=30 基准值=90
15 17 30 46 56 82 90 95
图9.5快速排序图示
从图9.4可以看出,作为基准值46多次与别的记录进行比较与交换。为了节约运算时
间,可先将其暂存于一个临时变量temp中,以后只移动其它记录,基准值不参与交换,直
到i=j时才将其放入正确位置。图9.5给出了快速排序的全部过程,递归形式的快速排序算
法的C语言实现为:
算法9.6
void quicksort(ElemType a[],int p,int q)
{
int i,j,temp;
i = p; j= q; temp = a[p];/*p,q分别为当前序列的下限和上限*/
while (i<j) {
/*越过不小于基准值的数据*/
while ((key(a[j])>=key(temp)) && (j>i)) j--;
if (j>i){
a[i] = a[j];
i++;
/*越过小于基准值的数据*/
while ((key(a[i])<=key(temp)) && (i<j)) i++;
if (i<j) {
a[j] = a[i]; j--;
}
}
}
a[i] = temp;
if ( p < (i-1) ) quicksort(a,p,i-1);/*快速排序左子序列*/
if ( (j+1) < q ) quicksort(a,j+1,q);/*快速排序右子序列*/
}
当基准值不能很好地分割数组,即基准值将数组分割成一个子数组中有一个记录,而另
一个子数组有n-1个记录时,下一次处理的子数组只比原来数组小1,这是快速排序的最差
情况。如果这种情况发生在每一次划分过程中,那么快速排序算法的时间复杂性为:
= O(n2)
当每个基准值都将数组分成相等的两部分,出现快速排序的最佳情况。在这种情况下,
我们还要对每个大小约为n/2的两个子数组进行排序。在一个大小为n的记录中确定一个记
录的位置所需要的时间为O(n)。若T(n)为对n个记录进行排序所需要的时间,则每当一
个记录得到其正确位置,数组大致分成两个相等的部分时,我们得到快速排序算法的最佳时
间复杂性
T(n) ? cn + 2T(n/2)
? cn + 2(cn/2 + 2T(n/4))
? 2cn + 4T(n/4))
.
.
.
? cnlogn + nT(1) = O(nlogn)
其中cn是一次划分所用的时间,c是一个常数。
快速排序的平均情况介于最佳与最差情况之间。假设,在每一次分割时,基准值处于最
终排好序的位置的概率是一样的,基准值将数组分成长度为0和n-1,1和n-2,…的概率
都是1/n。在这种假设下,快速排序算法的平均时间复杂性为:
T(n) = cn + 1/n (T(k) + T(n-1-k)) T(0) = c , T(1) = c
这是一个递归公式。T(k)和T(n-k-1)是指处理长度为k和n-k-1数组时快速排序算法所
花费的时间。根据公式所推算出来的时间为O(nlogn)。因此,快速排序平均时间复杂性为
O(nlogn)。
快速排序需要栈空间来实现递归,如果像上述分析所说,数组按均等方式被分割时,则
最大递归深度为logn,需要的栈空间为O(logn)。最坏的情况是,在递归的每一级上,数组
分割成长度为0的左子数组和长度为n-1的右子数组。在这种情况下,递归的深度就成为n,
需要的栈空间为O(n)。