两两比较待排序记录的关键码,如果发生逆序(即排列顺序与排序后的次序正好相反),则交换之,直到所有记录都排好序为止 。
交换排序的主要算法有:
(1) 冒泡排序
(2) 快速排序
通过一趟排序将待排序列以枢轴为标准划分成两部分,使其中一部分记录的关键字均比另一部分小,再分别对这两部分进行快速排序,以达到整个序列有序。
通常取第一个记录的值为基准值或枢轴。
附设两个指针low和high,初值分别指向第一个记录和最后一个记录,设枢轴为key;
1.从high 所指位置起向前搜索,找到第一个不大于基准值的记录与枢轴记录相互交换;
2.从low 所指位置起向后搜索,找到第一个不小于基准值的记录与枢轴记录相互交换。
3.重复这两步直至为止。
例:以关键字序列(256,301,751,129,937,863,742,694,076,438)为例
写出执行快速算法的各趟排序结束时,关键字序列的状态。
初始队列 | 256 | 301 | 751 | 129 | 937 | 863 | 742 | 694 | 076 | 438 |
---|---|---|---|---|---|---|---|---|---|---|
第一趟 | 【076 | 129】 | 256 | 【751 | 937 | 863 | 742 | 694 | 301 | 438】 |
第二趟 | 076 | 129 | 256 | 【438 | 301 | 694 | 742】 | 751 | 【863 | 937】 |
第三趟 | 076 | 129 | 256 | 【301】 | 438 | 【694 | 742】 | 751 | 863 | 937 |
第四趟 | 076 | 129 | 256 | 301 | 438 | 694 | 742 | 751 | 863 | 937 |
首先我们默认每一组数据的枢轴为它第一个元素。即将初始队列中的第一个元素:256设置为枢轴。
我们这里将,low或high的值改变到其他位置后,将low或high设为0,以方便辨别每一步步骤。
将low = 256,high = 438,key = 256;
先从high处开始:
① key < high ,所以位置不变,将 high- - 得:low = 256,high = 076
② key > high ,所以交换位置,将 low= high,同时将high处的值设为0得:low = 076,high = 0
即:
076 301 751 129 937 863 742 694 0 438
当high的位置改变后,再从low处开始:
③ key > low ,所以位置不变,将 low = 301,high = 0
④ key < low ,所以交换位置,将 low = 0,high = 301
即:
076 0 751 129 937 863 742 694 **301 ** 438
当low的位置改变后,再从high处开始:
⑤ key < high ,所以位置不变,low = 0,high = 694
⑥ key < high ,所以位置不变,low = 0,high = 742
⑦ key < high ,所以位置不变,low = 0,high = 863
⑧ key < high ,所以位置不变,low = 0,high = 937
⑨ key < high ,所以位置不变,low = 0,high = 129
⑩ key > high ,所以交换位置,low = 129,high = 0
即:
076 129 751 0 937 863 742 694 301 438
当high的位置改变后,再从low处开始:
⑪ key > low ,所以位置不变,low = 751,high = 0
⑫key < low ,所以交换位置,low = 0 ,high = 751
076 129 0 751 937 863 742 694 301 438
当low和high都搜算到一个位置的时候,说明第一趟查询结束,所以将low和high所指的地方赋值为key=256;
得到 076 129 256 751 937 863 742 694 301 438
由上可知:
在枢轴256 左边的都是小于256的,在枢轴256 右边的都是大于256的。
之后再将左边和右边分为两个序列,分别进行如上的快速排序步骤。
之后的步骤只需重复上面的步骤,直到全部排列完成。
int Partition (Elem R[ ], int low, int high){
R[0] = R[low]; pivotkey = R[low].key;
while (low < high) { //从两端交替向中间扫描
while (low < high && R[high].key >= pivotkey) - - high;
R[low] = R[high]; //将比枢轴记录小的移到低端
while (low < high && R[low].key <= pivotkey) + + low;
R[high] = R[low]; //将比枢轴记录大的移到高端
}
R[low] = R[0]; //枢轴记录到位
return low; //返回枢轴位置
}
void QSort ( Elem R[ ], int low, int high ){ //对序列R[low...high]进行快速排序
if (low < high-1) { //长度大于1
pivot = Partition( L,low,high); //将R[low..high]一分为二
QSort(L,low, pivot-1); //对低子表递归排序,pivo是枢轴
QSort(L, pivot+1, high); // 对高子表递归排序
}
}
void QuickSort(Elem R[], int n){ //对记录序列进行快速排序
QSort(R, 1, n);
}
•可以证明,函数Quicksort的平均计算时间是O(nlog2n)。实验结果表明:就平均计算时间而言,快速排序是我们所讨论的所有内排序方法中最好的一个。
•快速排序是递归的,需要有一个栈存放每层递归调用时的指针和参数(新的low和high)。最大递归调用层次数与递归树的深度一致,理想情况为 。因此,要求存储开销为 O(log2n)。
如果每次划分对一个对象定位后,该对象的左侧子序列与右侧子序列的长度相同,则下一步将是对两个长度减半的子序列进行排序,这是最理想的情况。此时,快速排序的趟数最少。
即待排序对象序列已经按其关键码从小到大排好序的情况下,其递归树成为单支树,每次划分只得到一个比上一次少一个对象的子序列。这样,必须经过 n-1 趟才能把所有对象定位,而且第 i 趟需要经过 n-i 次关键码比较才能找到第 i 个对象的安放位置,总的关键码比较次数将达到n^2/2
时间效率:O(nlog2n) —因为每趟确定的元素呈指数增加
空间效率:O(log2n)—因为算法的递归性,要用到栈空间
稳 定 性:不稳定 —因为可选任一元素为支点。
答:——基本上是!因为每趟可以确定的数据元素是呈指数增加的!
设每个子表的支点都在中间(比较均衡),则:
第1趟比较,可以确定1个元素的位置;
第2趟比较(2个子表),可以再确定2个元素的位置;
第3趟比较(4个子表),可以再确定4个元素的位置;
第4趟比较(8个子表),可以再确定8个元素的位置;
……
综上只需 ( └ log2n ┘ + 1 ) 趟便可排好序。
而且,每趟需要比较和移动的元素也呈指数下降,加上编程时使用了交替逼近技巧,更进一步减少了移动次数,所以速度特别快。