对快速排序的一点小探究

  快速排序算法是一种划分交换的方法,采用了分治法进行排序。

1 public static void quikSort(int a[],int left,int right)
2     {
3         if(left >= right)return;
4         int p = partition(a,left,right);
5         quikSort(a,left,p-1);
6         quikSort(a,p+1,right);
7     }

  开始,快速排序中划分方法partition()的基准元素都选取为最左侧 left 处的值:

 1 public static int partition(int a[],int left,int right)
 2     {
 3         int index =  left;
 4         int value = a[index];//基准元素
 5         while(left < right)
 6         {
 7             //比较中带等号,针对重复元素如:4,3,4,检测指针才会移动,不然就死循环了
 8             while(left < right && a[right] >= value)
 9             {
10                 right--;
11             }
12             a[left] = a[right];
13 //            if(left < right)
14 //            {
15 //                //需要比较一次left和right是否重合。虽然可以避免了它自己的值与自己比较,但是多了一次判断比较;效果上没有改进
16 //                left++;
17 //            }
18             //left++;//直接动left指针就是错误。当left与right指针重合时,这句话执行就不对,强制的多加了一次!重合之前都是对的
19             while(left < right && a[left] <= value)
20             {
21                 left++;
22             }
23             a[right] = a[left];
24         }
25         //跳出循环必然是left==right了,left和right重合就找到了基准元素就位时的位置
26         a[left] = value;
27 //System.out.println(" 划分点(该元素最终位置): " + left);
28         return left;
29     }

  后来,想到使用随机值,在某些程度上(比如在基本已经有序的情况下)改进快速排序(以下代码执行结果是错误的),如下:

 1 public static int partition2(int a[],int left,int right)
 2 {
 3
 4         int index =  left + r.nextInt(right - left + 1);//随机选基准元素
 5         int value = a[index];//用value保存了当前选取的枢轴元素,就可腾空一个位置
 6         while(left < right)
 7         {
 8             //比较中带等号,针对重复元素如:4,3,4,检测指针才会移动,不然就死循环了
 9
10             //先让右侧开始检测,对于4,9;选了9当value,直接开始right--就不对
11             while(left < right && a[right] >= value)
12             {
13                 right--;
14             }
15             a[index] = a[right];
16             index = right;
17             while(left < right && a[left] <= value)
18             {
19                 left++;
20             }
21             a[right] = a[left];
22             index = left;
23         }
24         a[left] = value;
25         return left;
26     }    

 

 结果思考,得知以上代码错误的原因:一定要把基准元素交换至最左边或最右边(放到最左就从right开始检测,最右就应该从left开始检测),使得腾空的位置从最边上位置,分别从最左最右两个方向,向基准元素就位的真正位置逼近,而不是一开始就从枢轴元素的原始位置index开始移动。

  修改代码如下即可:

 1 public static int partition(int a[],int left,int right)
 2     {
 3
 4         int index =  left + r.nextInt(right - left + 1);//随机选基准
 5         int value = a[index];
 6         //交换随即选取的基准元素至最左侧left处;并且就必须要先从right开始检测;如【4,9,4】,假如选取了a[0] = 4为基准元素,又从left开始检测就错误了。
 7         int temp = a[left];
 8         a[left] = a[index];
 9         a[index] = temp;
10
11         while(left < right)
12         {
13
14             while(left < right && a[right] >= value)
15             {
16                 right--;
17             }         a[left] = a[right];
18             while(left < right && a[left] <= value)
19             {
20                 left++;
21             }
22             a[right] = a[left];
23         }
24         a[left] = value;
25         return left;
26     }

 

 另一种划分函数的实现方式,从一个方向确定基准元素的最终位置(每次发现一个比基准元素小的元素,就通过使用index挨个从最前往后放。从前往后统统扫描一遍,把所有小于value的都放前面了,自然大于value的都在后面):

 1 public static int partition(int a[],int left,int right)
 2 {
 3   int index = left;
 4   int value = a[left];
 5 //从左端向右端一直检测
 6   for(int i=left+1;i<=right;i++)
 7   {
 8     if(a[i] < value)
 9     {
10        index++;
11       if(index != i)
12       {
13           swap(a[i],a[index]);
14       }
15      }
16   }
17     a[left] = a[index];
18     a[index] = value;//基准元素就位
19
20     return index;
21 }            

  

  总结:快速排序定位轴的位置,关键就看你是怎么做到,小于枢轴元素(基准元素)值的放到枢轴左边,大于枢轴元素值的放到枢轴右边;用腾空一个位置的办法也好,用swap的想法也好,都是如此。

  扩展思考:

一、考虑使用双向链表,设置头指针和尾指针,与基准元素值进行比较后,(拔下结点)在头结点之前插入小的,在尾结点之后插入大的。

二、排序的改进算法:(序列长度在5~25时)采用直接插入排序对划分后的子序列排序进行排序。

时间: 2024-08-18 11:19:32

对快速排序的一点小探究的相关文章

做预解释题的一点小方法和小技巧

在JavaScript中的函数理解中预解释是一个比较难懂的话题.原理虽然简单,寥寥数言,但其内涵却有深意,精髓难懂.如何在轻松活跃的头脑中将它学会,现在针对我在学习中的一点小窍门给大家分享一下,希望能给大家一些帮助: 万事需遵循"原理"--"预解释"无节操和"this"指向:(可先看例题解析然后结合原理进行学习) (感谢蕾蕾老师给归纳的预解释无节操原理:) 如果函数传参数则先于以下执行,就相当于在函数私有作用域下var了一个变量:根据作用域原理,

新闻发布的一点小总结

经过一段时间的学习,完成了新闻发布的基础功能,进行一点小总结,方便日后回顾.下面是我的一点小总结,不足之处请勿见笑... 我们想要完成一个新闻发布,首先要使其能够成功发布,并且让它能够实现添加.删除.修改.查询.上传.下载等功能.我们还调用AJAX功能查看输出为XML.JSON格式的新闻内容.为完成以上功能,我们首先要进行jdk.tomcat.eclipse的安装和配置.1.各软件的安装和配置1.1.jdk的安装和配置: 1.1.1.下载jdk:下载地址:http://www.oracle.co

【搬运工】一点小收集

1.几种基础算法. 2.数学之美. 3.贝叶斯方法. 转自互联网,链接背后都是故事,水深,都是鱼. 敬意且谨以自勉,长途漫漫,任重道远. 以上. [搬运工]一点小收集,布布扣,bubuko.com

Lichee (六) 配置内核时的一点小优化

我们在分析<Lichee(二) 在sun4i_crane平台下的编译 >的时候,居然没有一个步骤是在配置内核 make ARCH=arm menuconfig 仔细的读过的代码的会发现,在build_kernel有这么一段话 if [ ! -e .config ]; then echo -e "\n\t\tUsing default config... ...!\n" cp arch/arm/configs/sun4i_crane_defconfig .config fi

给Javascript初学者的一点小建议

本文来自e良师益友网 一般初学JavaScript的时候最头痛的就是浏览器兼容问题.在Firefox下面好好的代码放到IE就不能显示了,又或者是在IE能正常显示的代码在firefox又报错了. 如果你正初学JavaScript并有着一样的处境的话建议你:初学JavaScript的时候无视DOM和BOM的兼容性,将更多的时间花在 了解语言本身(ECMAScript).只在特定浏览器编写代码(Chrome/Firefox/Safari),实际工作中使用成熟的 JavaScript框架(jQuery等

C++ 中有关const引用的一点小知识

在读<C++ Primer>时,发现之前对const的概念不是很清晰,只知道如何去使用,于是翻开const引用部分又阅读了一遍,发现有两点自己要注意的地方 1.const限定的对象不可以初始化非const引用 ex. 1 const int src = 512; 2 const int &ok_dest = src; //ok: 引用和初始化对象都是const 3 int &err_dest = src; //error : 引用为非const 原因很简单, src 是不可以被

一点小心得

项目中会遇到这样的逻辑处理:根据不同类型调用不同的方法,通常会用到if else等语句,感觉不太好: 1,应该面向接口编程 2,尽量避免使用if语句 实例:原来代码,接口Iservice的实现类有 ServiceA ,ServiceB,ServiceC public static void main(String[] args) { String type = "C"; Iservice service = null; if (type.equals("A")) {

程序开发的一点小总结

程序开发的一点小总结, 给要学习一门新语言的朋友一些帮助, :P 1.多项条件下的处理 第一种方法: 每个需要执行A函数的条件下都写一边A函数调用, 这种方式也是最中规中矩的写法, 代码相对臃肿, 如果A有任何变动, 就要修改多处, 这种代码块写多了, 容易漏掉 if(b==1) a() else if(b==2) else if(b==3) a() else a() 第二种写法: 在B条件筛选前, 创建一个临变量布尔c, 用来监控需要A函数需要的条件, 需要就为true, 不需要就不写(默认初

8-22发现的一点小问题(在“引用”上出的问题)

今天写程序是发现了一点小纰漏: 程序的大体结构如下: Public class act extends Activity { //保存数据的list Private ArrayList<T> listInAct = null; Private ListView listView = null; } Public void onCreate(Bundle b){ listInAct = new ArrayList<T>(); .............................