快速排序中的分割算法的解析与应用

快速排序中的分割算法的解析与应用

一,分割(partition)算法介绍

所谓分割算法,先选定一个枢轴元素,然后 将数组中的元素分成两部分:比枢轴元素小的部分都位于枢轴元素左边;比枢轴元素大的部分都位于枢轴元素右边

此时,枢轴元素在数组中的位置就被“永久地确定”下来了—将整个数组排序,该枢轴元素的位置不会变化。

另外,枢轴元素的选取对分割算法至关重要。一般而言,终极追求的是:将数组平分。因此,尽可能地让枢轴元素的选取随机化和靠近中位数。

这里采用“三数取中”法选取枢轴元素。

关于快速排序排序算法,可参考:排序算法总结之快速排序

 

二,分割算法的实现

 1 //分割数组,将数组分成两部分. 一部分比pivot(枢轴元素)大,另一部分比pivot小
 2     private static int parition(int[] arr, int left, int right){
 3         
 4         int pivot = media3(arr, left, right);
 5         int i = left;
 6         int j = right - 1;//注意 ,在 media3()中 arr[right-1]就是 pivot
 7         
 8         for(;;)
 9         {
10             while(arr[++i] < pivot){}
11             while(arr[--j] > pivot){}
12             if(i < j)
13                 swap(arr, i, j);
14             else
15                 break;
16         }
17         
18         swap(arr, i, right-1);//restore pivot, 将枢轴元素放置到合适位置:arr左边元素都比pivot小,右边都比pivot大
19         return i;// 返回 pivot的 索引
20     }

①第4行,枢轴元素是通过“三数取中”法选择的。在“三数取中”时,还做了一些优化:将 枢轴元素 放到 数组末尾的倒数第二个位置处。具体参考 media3()
需要注意的是:当输入的数组中长度为1 或者 2 时, partition会出现向下越界(但对快排而言,当数组长度很小的,其实可以不用 partition,而是直接用插入排序)。因此,可加入以下的修改。

 1 //分割数组,将数组分成两部分. 一部分比pivot(枢轴元素)大,另一部分比pivot小
 2     private static int parition(int[] arr, int left, int right){
 3         
 4         int pivot = media3(arr, left, right);
 5         int i = left;
 6         int j = right - 1;//注意 ,在 media3()中 arr[right-1]就是 pivot
 7         
 8         //应对特殊情况下的数组,比如数组长度 小于3
 9         if(i >= j)
10             return i;
11         
12         for(;;)
13         {
14             while(arr[++i] < pivot){}
15             while(arr[--j] > pivot){}
16             if(i < j)
17                 swap(arr, i, j);
18             else
19                 break;
20         }
21         
22         swap(arr, i, right-1);//restore pivot 将枢轴元素放置到合适位置:arr左边元素都比pivot小,右边都比pivot大
23         return i;// 返回 pivot的 索引
24     }

 

再来看看,三数取中算法,这里也有个特殊情况:当数组中元素个数都没有3个时….怎么办?

 1     //三数取中,用在快排中随机选择枢轴元素时
 2     private static int media3(int[] arr, int left, int right){
 3         if(arr.length == 1)
 4             return arr[0];
 5         
 6         if(left == right)
 7             return arr[left];
 8         
 9         int center = (left + right) / 2;
10         
11         //找出三个数中的最小值放到 arr[left]
12         if(arr[center] < arr[left])
13             swap(arr, left, center);
14         if(arr[right] < arr[left])
15             swap(arr, left, right);
16         
17         //将 中间那个数放到 arr[media]
18         if(arr[center] > arr[right])
19             swap(arr, center, right);
20         
21         swap(arr, center, right-1);//尽量将大的元素放到右边--将privot放到右边, 可简化 分割操作(partition).
22         return arr[right-1];//返回中间大小的那个数
23     }

其实,这里的“三数取中”的实现,与参考资料中提到的三数取中实现有一点不同。这是正常的,毕竟实现细节不同。如果有错误,需要自行调试。

这里提下第3-7行的两个if语句:当需要 “取中”的目标数组长度为1时,或者说 对数组中某些范围内[left, right]的元素进行“取中”时,若left=right,则根本就没有3个数,违背了“三数取中”的本意(随机地选取枢轴元素),故直接 return。

当数组中元素只有一个时,第18行会越界。为了防止这种情况,在第3-4行就先对数组长度进行判断。当数组中只有两个元素,其实就相当于 center=left,因此,程序也没问题。

 

三,分割算法的应用

给定一个数组,数组中某个元素出现的次数超过了数组大小的一半,找出这个元素。

比如输入:[2,5,4,4,5,5,5,6,5] ,输出 5

这个问题,其实可以转化成求解中位数问题。因为,当数组有序时,出现次数超过一半的那个元素一定位于数组的中间。

所谓中位数,就是 假设 数组是有序的情况下,中间那个元素。即 arr[arr.length/2]

而要求解中位数,当然可以先对数组进行排序,但排序的时间复杂度为O(NlogN),那有没有更快的算法?

当然是有的。就是借助partition分割算法 来 实现。

 1 //找出 arr 中 第  n/2  大的那个元素
 2     public static int media_number(int[] arr){
 3         int left = 0;
 4         int right = arr.length - 1;
 5         int center = (left + right) / 2;
 6         
 7         int pivot_index = parition(arr, left, right);//枢轴元素的数组下标
 8         
 9         while(pivot_index != center)
10         {
11             if(pivot_index > center){
12                 right = pivot_index - 1;
13                 pivot_index = parition(arr, left, right);
14             }
15             else{
16                 left = pivot_index + 1;
17                 pivot_index = parition(arr, left, right);
18             }
19         }
20         return arr[center];
21     }

上面算法不仅可以求解“找出超过一半的数字”,也可以求解任何一个数组的中位数。

这里递归表达式 T(N)=T(N/2)+O(N),O(N)表示将数组 分成两部分所花的代价。

故时间复杂度为O(N)

 

四,参考资料

排序算法总结之快速排序

 整个完整代码

public class Middle_Large {
    
    //找出 arr 中 第  n/2  大的那个元素
    public static int media_number(int[] arr){
        int left = 0;
        int right = arr.length - 1;
        int center = (left + right) / 2;
        
        int pivot_index = parition(arr, left, right);
        
        while(pivot_index != center)
        {
            if(pivot_index > center){
                right = pivot_index - 1;
                pivot_index = parition(arr, left, right);
            }
            else{
                left = pivot_index + 1;
                pivot_index = parition(arr, left, right);
            }
        }
        return arr[center];
    }
    
    //分割数组,将数组分成两部分. 一部分比pivot(枢轴元素)大,另一部分比pivot小
    private static int parition(int[] arr, int left, int right){
        
        int pivot = media3(arr, left, right);
        int i = left;
        int j = right - 1;//注意 ,在 media3()中 arr[right-1]就是 pivot
        
        //应对特殊情况下的数组,比如数组长度 小于3
        if(i >= j)
            return i;
        
        for(;;)
        {
            while(arr[++i] < pivot){}
            while(arr[--j] > pivot){}
            if(i < j)
                swap(arr, i, j);
            else
                break;
        }
        
        swap(arr, i, right-1);//restore pivot 将枢轴元素放置到合适位置:arr左边元素都比pivot小,右边都比pivot大
        return i;// 返回 pivot的 索引
    }
    
    
    //三数取中,用在快排中随机选择枢轴元素时
    private static int media3(int[] arr, int left, int right){
        if(arr.length == 1)
            return arr[0];
        
     if(left == right)
return arr[left];
int center = (left + right) / 2; //找出三个数中的最小值放到 arr[left] if(arr[center] < arr[left]) swap(arr, left, center); if(arr[right] < arr[left]) swap(arr, left, right); //将 中间那个数放到 arr[media] if(arr[center] > arr[right]) swap(arr, center, right); swap(arr, center, right-1);//尽量将大的元素放到右边--将privot放到右边, 可简化 分割操作(partition). return arr[right-1];//返回中间大小的那个数 } private static void swap(int[] arr, int left, int right){ int tmp = arr[left]; arr[left] = arr[right]; arr[right] = tmp; } public static void main(String[] args) { int[] arr = {5,6,8,4,1,5,5,5,5}; int result = media_number(arr); System.out.println(result); } }

 

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

发布者:全栈程序员-用户IM,转载请注明出处:https://javaforall.cn/108971.html原文链接:https://javaforall.cn

【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛

【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...

(0)


相关推荐

  • python 可视化:fig, ax = plt.subplots()画多表图的3中常见样例 & 自定义图表格式

    python 可视化:fig, ax = plt.subplots()画多表图的3中常见样例 & 自定义图表格式目录一、fig,ax=plt.subplots()的作用?二、参数的含义?三、怎么在一个图上排列多个子图?四、怎么把多个子图一起合并到一个图上?《PythonforDataAnalysis》2ndEdition一、fig,ax=plt.subplots()的作用?它是用来创建总画布/figure“窗口”的,有figure就可以在上边(或其中一个子…

  • 所谓的CS和BS_CS程序

    所谓的CS和BS_CS程序    我们在步入CSharp之后,新接触了CS和BS这两个概念,今天小编就给大家分享一下有关CS和BS的知识,如有雷同不胜荣幸  CS:即Cilent/Sever(客户机/服务器)结构,CS在技术上很成熟,主要特点是交互性强,具有安全的存取模式,响应速度快,利于处理大量数据,但是灵活性不好,管理和维护费用高,通常用于小型局域网络。  BS:即Browser/Sever(浏览器/服务器)结…

  • 运算放大电路在音频放大电路中的应用研究与实现「建议收藏」

    运算放大电路在音频放大电路中的应用研究与实现「建议收藏」1、导言放大电路是构成各种功能模拟电路的基础电路,也是对模拟信号最基本的处理。音频信号可以分解成若干频率的正玄波之和,其频率分为在20Hz~20KHz。不当的放大电路会造成音频信号的失真,亦会带来干扰和噪声。所有电子信息系统组成的原则都应包含:1、满足功能和性能要求,2、尽量简单,3、电磁兼容,4、调试应用简单。因此本文就来研究在不会增大电路复杂度的前提下,如何实现音频信号放大的同时对信号进…

  • 怎么使用virsh命令_isql命令

    怎么使用virsh命令_isql命令virsh.c中main->vshParseArgv->vshCommandArgvParse->vshCommandParse->vshCmddefSearchintmain(intargc,char**argv){if(!vshParseArgv(ctl,argc,argv)){vshDeinit(ctl);exit(EXIT_FAILURE);}staticboolvshParseA…

  • 红帽子虚拟机安装教程_红帽子联网后怎么安装gcc

    红帽子虚拟机安装教程_红帽子联网后怎么安装gcc在WindowsXP中安装虚拟机VMware的方法跟普通软件基本一致,需要填入注册密钥.安装完成以后就可以安装红帽子企业版5操作系统了,首先需要新建一台虚拟的计算机,为其分配内存,硬盘空间,CPU等信息,然后选择启动的位置,是硬盘中的ISO文件还是光驱中的启动,然后就可以启动虚拟机进行安装了.在安装红帽子企业版时选择定制安装,作为初学者可以选择把所有的东西都安装上,这…

  • Java零基础快速入门的方法

    Java零基础快速入门的方法各种各样的编程语言不断崛起,但唯有Java是牢牢占据着老大的位置,目前几乎90%以上的大中型互联网应用系统在服务器端开发首选Java。因此,也是吸引了不少年轻人投入到Java的学习之中。​所以,今天就跟大家分享一份系统的Java学习教程路线图,零基础也可以无压力的走进Java,学习Java!第一阶段:Java语言入门到精通学习任何一门编程语言,首先要学习的是基础语法,开启Java学习的第一步,当然就是深入掌握计算机基础、编程基础语法,面向对象,集合、IO流、线程、并发、异常及网络编程,这些我

发表回复

您的电子邮箱地址不会被公开。

关注全栈程序员社区公众号