详谈内部排序之快速排序

快速排序

交换排序:

​ 两两比较待排序记录的关键码,如果发生逆序(即排列顺序与排序后的次序正好相反),则交换之,直到所有记录都排好序为止 。

​ 交换排序的主要算法有:

​ (1) 冒泡排序

​ (2) 快速排序


快速排序基本思想:

(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 ) 趟便可排好序。

​ 而且,每趟需要比较和移动的元素也呈指数下降,加上编程时使用了交替逼近技巧,更进一步减少了移动次数,所以速度特别快。

你可能感兴趣的:(笔记,算法,数据结构,快速排序)