快速排序的递归和非递归分析



1、算法思想

     快速排序是C.R.A.Hoare于1962年提出的一种划分交换排序。它采用了一种分治的策略,通常称其为分治法(Divide-and-ConquerMethod)。

(1) 分治法的基本思想

 分治法的基本思想是:将原问题分解为若干个规模更小但结构与原问题相似的子问题。递归地解这些子问题,然后将这些子问题的解组合为原问题的解。

(2)快速排序的基本思想

 设当前待排序的无序区为R[low..high],利用分治法可将快速排序的基本思想描述为:

①分解:

 在R[low..high]中任选一个记录作为基准(Pivot),以此基准将当前无序区划分为左、右两个较小的子区间R[low..pivotpos-1)和R[pivotpos+1..high],并使左边子区间中所有记录的关键字均小于等于基准记录(不妨记为pivot)的关键字pivot.key,右边的子区间中所有记录的关键字均大于等于pivot.key,而基准记录pivot则位于正确的位置(pivotpos)上,它无须参加后续的排序。

注意:

划分的关键是要求出基准记录所在的位置pivotpos。划分的结果可以简单地表示为(注意pivot=R[pivotpos]):

R[low..pivotpos-1].keys≤R[pivotpos].key≤R[pivotpos+1..high].keys

其中low≤pivotpos≤high。

②求解:

  通过递归调用快速排序对左、右子区间R[low..pivotpos-1]和R[pivotpos+1..high]快速排序。

③组合:

 因为当"求解"步骤中的两个递归调用结束时,其左、右两个子区间已有序。对快速排序而言,"组合"步骤无须做什么,可看作是空操作。

2、快速排序算法QuickSort

(1)递归算法

注:下面的算法是经过了很多的考虑之后达到了一个非常完美的解决方案,比很多书上写的要完整,主要是考虑了数组越界的情况

template<class T>

void QuickSort(T a[],int left,int right)

{

if (left >= right)

return;

int l = left+1,

r = right;

int pivot = a[left];

while (l < r)

{

while (l < r&&a[l] <= pivot)//为防止数组越界,必须加上限制条件l < r

++l;

while (r > left&&a[r] >= pivot)//同上防止数组越界,须加上限制条件r>left

--r;

if (l < r)

{

T tmp = a[l];

a[l] = a[r];

a[r] = tmp;

}

}

if (a[r] < pivot)//此条件很重要,当l和r相等时,我们需要判断是否需要交换

{

a[left] = a[r];

a[r] = pivot;

}

QuickSort(a,left,r-1);

QuickSort(a,r+1,right);

}

(2)非递归的解决方案

template<class T>

struct sPoint

{

T* p;

int index;

}

template<class T>

void QuickSort2(T a[],int n)

{

sPoint<T> sp;

stack<sPoint<T> > ss;

while (1)

{

if (2 == n)

{

if (a[0] > a[1])

Swap(a[0],a[1]);

if (ss.empty())

break;

sp = ss.top();

ss.pop();

a = sp.p;

n = sp.index;

continue;

}

T* l = a+1;

T* r = a+n-1;

while (l < r)

{

while (l < r&&*r <= *a)

++l;

while (r > a&&*r >= *a)

--r;

if (l < r)

Swap(*l,*r);

}

if (*r < *a)

Swap(*r,*a);

sp.p = r+1;

sp.index = n-(r-a+1);

ss.push(sp);

n = r-a;

}

}

3、算法分析

     快速排序的时间主要耗费在划分操作上,对长度为k的区间进行划分,共需k-1次关键字的比较。

(1)最坏时间复杂度

 最坏情况是每次划分选取的基准都是当前无序区中关键字最小(或最大)的记录,划分的结果是基准左边的子区间为空(或右边的子区间为空),而划分所得的另一个非空的子区间中记录数目,仅仅比划分前的无序区中记录个数减少一个。

 因此,快速排序必须做n-1次划分,第i次划分开始时区间长度为n-i+1,所需的比较次数为n-i(1≤i≤n-1),故总的比较次数达到最大值:

Cmax = n(n-1)/2=O(n2)

 如果按上面给出的划分算法,每次取当前无序区的第1个记录为基准,那么当文件的记录已按递增序(或递减序)排列时,每次划分所取的基准就是当前无序区中关键字最小(或最大)的记录,则快速排序所需的比较次数反而最多。

(2) 最好时间复杂度

 在最好情况下,每次划分所取的基准都是当前无序区的"中值"记录,划分的结果是基准的左、右两个无序子区间的长度大致相等。总的关键字比较次数:

0(nlgn)

注意:

 用递归树来分析最好情况下的比较次数更简单。因为每次划分后左、右子区间长度大致相等,故递归树的高度为O(lgn),而递归树每一层上各结点所对应的划分过程中所需要的关键字比较次数总和不超过n,故整个排序过程所需要的关键字比较总次数C(n)=O(nlgn)。

 因为快速排序的记录移动次数不大于比较的次数,所以快速排序的最坏时间复杂度应为0(n2),最好时间复杂度为O(nlgn)。

(3)基准关键字的选取

 在当前无序区中选取划分的基准关键字是决定算法性能的关键。

  ①"三者取中"的规则

 "三者取中"规则,即在当前区间里,将该区间首、尾和中间位置上的关键字比较,取三者之中值所对应的记录作为基准,在划分开始前将该基准记录和该区伺的第1个记录进行交换,此后的划分过程与上面所给的Partition算法完全相同。

  ②取位于low和high之间的随机数k(low≤k≤high),用R[k]作为基准

 选取基准最好的方法是用一个随机函数产生一个取位于low和high之间的随机数k(low≤k≤high),用R[k]作为基准,这相当于强迫R[low..high]中的记录是随机分布的。用此方法所得到的快速排序一般称为随机的快速排序算法。

注意:

     随机化的快速排序与一般的快速排序算法差别很小。但随机化后,算法的性能大大地提高了,尤其是对初始有序的文件,一般不可能导致最坏情况的发生。算法的随机化不仅仅适用于快速排序,也适用于其它需要数据随机分布的算法。

(4)平均时间复杂度

 尽管快速排序的最坏时间为O(n2),但就平均性能而言,它是基于关键字比较的内部排序算法中速度最快者,快速排序亦因此而得名。它的平均时间复杂度为O(nlgn)。

(5)空间复杂度

 快速排序在系统内部需要一个栈来实现递归。若每次划分较为均匀,则其递归树的高度为O(lgn),故递归后需栈空间为O(lgn)。最坏情况下,递归树的高度为O(n),所需的栈空间为O(n)。

(6)稳定性

 快速排序是非稳定的。

快速排序的递归和非递归分析

时间: 2024-11-05 17:32:27

快速排序的递归和非递归分析的相关文章

快速排序(递归及非递归算法源码)

1. 递归算法: quicksort.cpp #include <iostream>using namespace std; void Swap(int a[],int i,int j){ int temp=a[i]; a[i] = a[j]; a[j] = temp;}int Partition(int a[],int low, int high){ int pivot=a[low]; while(low<high) { while(low<high && a[h

每天刷个算法题20160525:快速排序的递归转非递归解法

版权所有.所有权利保留. 欢迎转载,转载时请注明出处: http://blog.csdn.net/xiaofei_it/article/details/51524798 为了防止思维僵化,每天刷个算法题.已经刷了几天了,现在发点代码. 我已经建了一个开源项目,每天的题目都在里面: https://github.com/Xiaofei-it/Algorithms 绝大部分算法都是我自己写的,没有参考网上通用代码.读者可能会觉得有的代码晦涩难懂,因为那是我自己的理解. 最近几天都是在写一些原来的东西

【数据结构】大量数据(20万)的快速排序的递归与非递归算法、三数取中思想

快速排序的挖坑法与prev.cur法,我们在上一篇博客的第6个排序中讲的非常详细,http://10740184.blog.51cto.com/10730184/1774508[数据结构]常用排序算法(包括:选择排序,堆排序,冒泡排序,选择排序,快速排序,归并排序) 有兴趣的话,相信聪明的你,一看就会秒懂快速排序的思想. 下面,我们将快速排序优化: 1.三数取中来优化快速排序 优化原因: 快速排序的擦差不多每次将序列一分为二,时间复杂度是O(n*lgn). 我们思考,快速排序的时间复杂度是O(n

快速排序的递归和非递归实现 -----C++代码实现

快速排序的基本思想是:通过一趟排序将待排记录分割成独立的两部分,其中一部分记录的关键字均比另一部分记录的关键字小,则可分别对这两部分记录继续进行排序,已达到整个序列有序. 快速排序是一种不稳定的排序方法,其平均时间复杂度为:O(NlogN),最坏的情况是O(N*N) 特别注意:快速排序中用到的Partition函数,它的作用是进行一趟快速排序,返回"参考目标"的最终位置p,经过Partition处理之后,p左边的记录关键字均不大于参考目标,p右边的记录关键字均不小于参考目标. Part

快速排序的递归和非递归

快速排序,顾名思义,是一种速度快,效率高的排序算法. 快排原理: 在要排的数(比如数组A)中选择一个中心值key(比如A[0]),通过一趟排序将数组A分成两部分,其中以key为中心,key右边都比key大,key左边的都key小,然后对这两部分分别重复这个过程,直到整个有序. 整个快排的过程就简化为了一趟排序的过程,然后递归调用就行了. 一趟排序的方法: 1定义i=0,j=A.lenght-1,i为第一个数的下标,j为最后一个数下标 2从数组的最后一个数Aj从右往左找,找到第一小于key的数,记

69 快速排序的递归和非递归实现

[本文链接] http://www.cnblogs.com/hellogiser/p/quicksort-recursively-and-non-recursively.html [代码] C++ Code 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777

递归与非递归及其相互转换

一.什么是递归 递归是指某个函数直接或间接的调用自身.问题的求解过程就是划分成许多相同性质的子问题的求解,而小问题的求解过程可以很容易的求出,这些子问题的解就构成里原问题的解了. 二.递归的几个特点 1.递归式,就是如何将原问题划分成子问题. 2.递归出口,递归终止的条件,即最小子问题的求解,可以允许多个出口. 3.界函数,问题规模变化的函数,它保证递归的规模向出口条件靠拢 三.递归的运做机制 很明显,很多问题本身固有的性质就决定此类问题是递归定义,所以递归程序很直接算法程序结构清晰.思路明了.

C语言递归,非递归实现翻转链表

翻转链表作为,链表的常用操作,也是面试常遇到的. 分析非递归分析: 非递归用的小技巧比较多,很容易出错. 递归分析比较简单,在代码里面 代码: #include<stdio.h> #include<stdlib.h> typedef int elemtype; typedef struct node{ elemtype element; struct node*next;//写成node* next;node * next;node *next;也是可以的,为了方便阅读,以后统一写

算法之归并排序的递归与非递归的实现

一.什么是归并排序 归并排序就是将多个有序的数据段合成一个有序的数据段,如果参与合并的只有两个有序的数据段,则称为二路归并.与快速排序和堆排序相比,其最大的特点是一种稳定的算法,算法的平均时间复杂度O(nlog2n). 二.归并排序的基本思路 (1).对于一个原始的待排序表,可以将R[1]到R[n]可以看做是n个长度为1的有序表,即分解. (2).进行第一趟归并,即将上述的n个子序两两合并,得到 n/2向上取整 个有序表,若n为奇数,则归并到最后一个子序列长度为1,即合并. (3).再将两个 n