partition函数是快排的核心部分

它的目的就是将数组划分为<=pivot和>pivot两部分,或者是<pivot和>=pivot

其实现方法大体有两种,单向扫描版本双向扫描版本,但是具体到某个版本,其实现方法也是千差万别,参差不齐。本着严谨治学的态度,我将目前所接触的所有实现列举出来,并作出比较。除了伪代码,我也会给出相应的C&C++实现,供读者参考。

单向扫描:

下面是算法导论中例子

PARTITION(A, p, r)
x = A[r]
i = p -
for j = p to r -
if A[j] <= x
i = i +
exchange A[i] with A[j]
exchange A[i + ] with A[r]
return i + int partition(int a[], int p, int r)
{
int x = a[r];
int i = p - ;
int j = p;
for (; j < r; ++j)
if (a[j] <= x)
swap(&a[++i], &a[j]);
swap(&a[i + ], &a[j]);
return i + ;
}

这个是标准的单向扫描,其思路是:

将小于或等于pivot的元素通过交换全部移到前面去,这里需要注意的是i的作用,这是个哨兵,用于记录交换后的位置,也就是i之前的元素都是交换好了的。

下面是一些可以变动的地方:

1.可以将小于pivot的元素移到前面去,而不是小于等于,这样可以减少些交换次数,同理,可以将大于pivot的元素移到后面去,不过这样就需要倒序遍历了

2.或者是将i的初始值设置为p,而不是p-1;

3.可以将pivot设置成第一个元素;

4.存在i=j的情况,这时候的交换就是多余的,可以优化掉。

下面是稍作优化的版本

int partition(int a[], int p, int r)
{
int x = a[r];
int i = p;
int j = p;
for (; j < r; ++j)
if (a[j] < x) {
if (i != j)
swap(&a[i], &a[j]);
i++;
}
swap(&a[i], &a[j]);
return i;
}

双向扫描:

算法导论上的课后题有该算法,但是错误百出,这里以《算法》第四版的方法为例

PARTITION(A, p, r)
x = A[p]
i = p
j = r +
while true
repeat
j = j -
until A[j] <= x
repeat
i = i +
until A[i] >= x
if i >= j
break
exchange A[i] with A[j]
exchange A[p] with A[j]
return j int partition(int a[], int p, int r)
{
int x = a[p];
int i = p;
int j = r + ;
while (true) {
while (a[--j] > x);
while (a[++i] < x);
if (i >= j)
break;
swap(&a[i], &a[j]);
}
swap(&a[j], &a[p]);
return j;
}

其思路是从左到右找到大于等于pivot的元素,从右到左找到小于等于pivot的元素,然后将这两个元素交换,直到左右扫描相遇,最后还要进行一次交换,将pivot调整到正确位置

这是上面程序的变种,看起来差别很大,不过原理是相同的

int partition(int a[], int p, int r)
{
int x = a[p];
int i = p + ;
int j = r;
while (i <= j) {
while (a[j] > x) j--;
while (a[i] < x) i++;
if (i >= j)
break;
swap(&a[i++], &a[j--]);
}
swap(&a[j], &a[p]);
return j;
}

我们看一下它的扫描条件,一个是大于等于,一个是小于等于,也就是说左右扫描点存在都等于pivot的情况,这时候我们是不用交换的。根据互补原理,一个扫描点条件是大于等于,那么另一扫描点条件应该是互补条件小于,这样两个扫描点交换就不会出现交换相等元素的情况。

另外程序还存在着巨大的溢出漏洞,内层的while循环如:

while (a[i] < x) i++;

我们无法保证其不会越界,事实上,我经过测试,发现i的值一旦越界就不确定了,虽然都能保证i >= j的临界条件,但我们还是应该尽量避免越界问题

可以在循环中加入越界条件

int partition(int a[], int p, int r)
{
int x = a[p];
int i = p;
int j = r + ;
while (true) {
while (i < j && a[--j] >= x);
if (i >= j) break;
while (i < j && a[++i] < x);
if (i >= j) break;
swap(&a[i], &a[j]);
}
swap(&a[j], &a[p]);
return j;
}

变种的防越界版如下

int partition(int a[], int p, int r)
{
int x = a[p];
int i = p + ;
int j = r;
while (true) {
while (i <= j && a[j] >= x) j--;
if (i > j) break;
while (i <= j && a[i] < x) i++;
if (i > j) break;
swap(&a[i++], &a[j--]);
}
swap(&a[j], &a[p]);
return j;
}

左右扫描的版本还有很多,让我们再来举几个例子

网上流传比较广的一个版本是下面这个

int partition(int a[], int p, int r)
{
int x = a[p];
int i = p;
int j = r;
while (i < j)
{
while (i < j && a[j] >= x) j--;
if (i >= j) break;
a[i++] = a[j];
while (i < j && a[i] < x) i++;
if (i >= j) break;
a[j--] = a[i];
}
a[i] = x;
return i;
}

仔细观察会发现,它与我们上面介绍的版本几乎如出一辙,不同的是,它没有使用swap交换元素,而是依次覆盖,最后再把pivot归位

具体过程可以参阅:http://blog.csdn.net/morewindows/article/details/6684558

算法的时间复杂度是O(n),但是为什么要写成双循环呢?我们完全可以把它改成单循环,代码如下:

int partition(int a[], int p, int r)
{
int x = a[p];
int i = p + ;
int j = r;
while (i <= j) {
if (a[j] > x)
{
j--;
continue;
}
if (a[i] < x)
{
i++;
continue;
}
swap(&a[i++], &a[j--]);
}
swap(&a[j], &a[p]);
return j;
}

但是,并不推荐这种做法,因为每次判断i的时候,势必会再次判断j,多一次比较。

总结:个人推荐单向扫描的优化版本,双向扫描可以看到会有越界的问题,为了防止越界付出了一定代价。

快速排序 partition函数的所有版本比较的更多相关文章

  1. 剑指Offer28 最小的K个数(Partition函数应用+大顶堆)

    包含了Partition函数的多种用法 以及大顶堆操作 /*********************************************************************** ...

  2. 寻找序列中最小的第N个元素(partition函数实现)

    Partition为分割算法,用于将一个序列a[n]分为三部分:a[n]中大于某一元素x的部分,等于x的部分和小于x的部分. Partition程序如下: long Partition (long a ...

  3. Partition函数

    快排中核心的方法应该算是Partition函数了,它的作用就是将整个数组分成小于基准值的左边,和大于基准值的右边. 普通的Partition函数是这样的: public static int part ...

  4. 字符串的partition函数

    partition函数 str1='sdga2a34'aa=str1.partition('a') print(aa) """ ('sdg', 'a', '2a34') ...

  5. c++多线程编程:实现标准库accumulate函数的并行计算版本

    今天使用c++实现了标准库头文件<numeric>中的accumulate函数的并行计算版本,代码如下,注释写的比较详细,仅对其中几点进行描述: ①该实现假定不发生任何异常,故没有对可能产 ...

  6. 快速排序中的partition函数的枢纽元选择,代码细节,以及其标准实现

    很多笔试面试都喜欢考察快排,叫你手写一个也不是啥事.我很早之前就学了这个,对快速排序的过程是很清楚的.但是最近自己尝试手写,发现之前对算法的细节把握不够精准,很多地方甚至只是大脑中的一个映像,而没有理 ...

  7. 快速排序的Partition函数

    1 //数组中两个数的交换 2 static void swap(int[] nums, int pos1, int pos2){ 3 int temp = nums[pos1]; 4 nums[po ...

  8. 快速排序partition过程常见的两种写法+快速排序非递归实现

    这里不详细说明快速排序的原理,具体可参考here 快速排序主要是partition的过程,partition最常用有以下两种写法 第一种: int mypartition(vector<int& ...

  9. C++关于strcpy等函数的安全版本

    如下程序: #include <iostream> using namespace std; int main() { ]; strcpy(ch1,"); } 在VS2012上面 ...

随机推荐

  1. Pomelo的component组件

    pomelo的核心是由一系列松耦合的component组成,同时我们也可以实现我们自己的component来完成一些自己定制的功能.对于我们的聊天应用,我们尝试给其增加一个component,目的是展 ...

  2. POJ1221(整数划分)

    UNIMODAL PALINDROMIC DECOMPOSITIONS Time Limit: 1000MS   Memory Limit: 65536K Total Submissions: 543 ...

  3. 蓝桥网试题 java 基础练习 十进制转十六进制

    ---------------------------------------------------------------------------------------------------- ...

  4. JavaScript嗅探执行神器-sniffer.js,你值得拥有!

    一.热身--先看实战代码 a.js 文件 // 定义Wall及内部方法 ;(function(window, FUNC, undefined){ var name = 'wall'; Wall.say ...

  5. iOS Storyboard适配问题

      通用的Storyboard 通用的stroyboard文件是通向自适应布局光明大道的第一步.在一个storyboard文件中适配iPad和iPhone的布局在iOS8中已不再是梦想.我们不必再为不 ...

  6. 细说Asp.Net WebAPI消息处理管道

    我们在开发完Asp.Net WebAPI程序后,可以使用WebHost寄宿方式或者SelfHost寄宿方式来部署Asp.Net WebAPI.所谓WebHost寄宿就是通过Asp.Net来实现:所谓S ...

  7. 搜查的PHPCMS,织梦DEDECMS 部分对比分析

    对于初学者来说,我想会有相对的了解和认识,自然也有助于今后前端面试的一些小细节!当下学的自然是phpcms phpcms优点: 1. 模块化安装,非常适合安装,拆卸非常方便的. 2. 灵活的标签语法, ...

  8. git文件夹下项目更改ip地址小结

    在我们开发的过程中,经常切换项目IP地址是很正常的,之前弄过一次,没有记住,现在简单的总结下: 找到要切换IP地址的项目,点击鼠标右键,弹出下图: 打开该项目的路径后,双击打开该项目,具体参考自己项目 ...

  9. [Hadoop] - TaskTracker源码分析(状态发送)

    TaskTracker节点向JobTracker汇报当前节点的运行时信息时候,是将运行状态信息同心跳报告一起发送给JobTracker的,主要包括TaskTracker的基本信息.节点资源使用信息.各 ...

  10. 华硕ASUS笔记本 中间5个指示灯分别表示什么

    1.图案像个灯的叫电源状态指示灯:当笔记本电脑启动时,电源状态指示灯便会亮起来并会在笔记本电脑进入休眠模式(Sleep Mode,Suspend-to-RAM)时缓慢闪烁.当笔记本电脑关机或进入休眠模 ...