快速排序(划分函数partition和主元pivot选取)

快排是一种基于分治思想的排序算法,在如此众多的排序算法中,其综合排序速度最佳(大多情况为O(nlog2n)),这也是快排之所以叫快排的一个原因之一。先来看一下快排的基本轮廓。

//快排函数
    static void quickSort(int[] arr, int begin, int end) {
        if (begin < end) {//分治区间大于2就继续划分
            //调用快排的内核函数partition确定主元位置,并将主元放到最终位置
            int pivot_loc = partition2(arr, begin, end);
            //分治左区间
            quickSort(arr, begin, pivot_loc - 1);
            //分治右区间
            quickSort(arr, pivot_loc + 1, end);
        }
    }

为什么快排会做到如此快速呢?从以上代码中可以发现,这是因为快排的每一次划分决定的,它的每次划分尽量做到从中间切开,使得在尽量少的划分次数下把原数组划分成最小的单元。划分函数的作用是把选定的主元放到最终的合适位置,并且保证主元位置的左边元素全部小于等于主元,右边元素全部大于主元,最终返回主元的位置。

一 partition划分函数
(1)单向扫描分区法

思想:这种划分是选定主元后,确定左右两个“指针”扫描待排数组,每一次循环都是判断左边指针所指元素是否小于等于主元,若是,则把左指针向右移一位,否则交换左右指针所指元素,并且右指针向左移动一步。直到左右指针交错后退出循环,这时右指针所指位置即为主元的最终位置,交换右指针所指元素与主元所指元素,返回主元所在下标即可。

//1.单向扫描分区法(左指针主动,右指针被动)
    static int partition1(int[] arr, int begin, int end) {
        int pivot = arr[begin];//确定主元为数组的第一个元素(默认)

        int left = begin + 1;//(左侧)扫描指针
        int right = end;//右侧指针
        while (left <= right) {
            if (arr[left] <= pivot) {//扫描元素小于等于主元,左指针向右移动
                left++;
            } else {//扫描元素大于主元,两指针元素交换,右指针左移
                swap(arr, left, right);
                right--;
            }
        }
        //左右指针交错后,主元找到最终位置(即:right所处位置)
        swap(arr, begin, right);//把主元放到最终位置
        return right;//返回主元位置
    }
(2)双向扫描分区法

思想:这种划分方法左右指针的地位更加平等,选定主元后,左指针在右指针的左边的前提下,当左指针所指元素小于等于主元时,就不断向右推进。右指针也是同理,当其所指元素大于主元时,就不断向左推进,直到左右指针相交便找到了主元的最终位置。这里需要注意的是,当左右指针各自在向对方方向推进的时候,不知道是否相交了,所以在推进指针的内层while循环中要加判断条件:左指针在右指针的左边。

//2.双向扫描分区法(两指正等地位,向中间扫描)
    static int partition2(int[] arr, int begin, int end) {
        getMid2(arr,begin,end);
        int pivot = arr[begin];//确定主元为数组的第一个元素
        int left = begin + 1;//左侧指针
        int right = end;//右侧指针
        while (left <= right) {
            //注意加left<=right条件,防止左右指针咋在外部while的内部相交
            while (left <= right && arr[left] <= pivot) left++;
            while (left <= right && arr[right] > pivot) right--;
            //注意:交换左右指针所指元素时也要判定left<=right
            //因为在上面两个while中可能导致left和right错位
            if (left <= right) swap(arr, left, right);
        }
        //退出while表示已经相交,找到了pivot的最终位置:right位置(由arr[left]<=pivot决定)
        swap(arr, begin, right);
        return right;//返回位置
    }

partition函数虽然是快排的“内核”,但真正影响快排效率的是输入数字的特征和主元的选取,数字特征我们没法控制,但我们可以通过控制主元的选取方法来控制数字特征带来的影响。

pivot主元的选取
(1)三点中值法

思想:这种选取主元的方法是选取待排数组中首,尾和中间位置这三个元素中第二大的元素作为主元,这样可以大概率的避免划分失衡带来的效率影响。

//三点中值法
    static void getMid1(int[] arr, int begin, int end) {
        int midIndex = (begin + end) / 2;//中间下标
        int midValueIndex = -1;//中值的下标

        if (arr[begin] <= arr[midIndex] && arr[begin] >= arr[end]) {
            midValueIndex = begin;
        } else if (arr[end] <= arr[midIndex] && arr[end] >= arr[begin]) {
            midValueIndex = end;
        } else {
            midValueIndex = midIndex;
        }
        //把中值放在数组的首位,便于取主元
        swap(arr, begin, midValueIndex);
    }
(2)绝对中值法

思想:把待排序数字序列分成五个一组(最后一组除外),把它们进行排序后选出各组的中值,再将这些中值排序后再一次选出一个最终中值作为主元。这种选取主元的方式可以保证时间复杂度始终稳定在O(nlog2n)而不含有运气成分。

//绝对中值法
    static int getMid2(int[] arr, int begin, int end) {
        int size = end - begin + 1;//数组长度
        //每五个元素一组
        int groupSize = (size % 5 == 0) ? (size / 5) : (size / 5 + 1);//组数
        //存储各小组的中值
        int medians[] = new int[groupSize];
        int indexOfMedians = 0;//medians[]数组的下标
        for (int i = 0; i < groupSize; i++) {
            //单独处理最后一组,因为可能不满五个元素
            if (i == groupSize - 1) {
                //此处应该单独写一个插入排序,这样调接口(偷懒)
                Arrays.sort(arr, begin + 5 * i, end);//排序最后一组
                medians[indexOfMedians++] = arr[(begin + i * 5 + end) / 2];//最后一组的中间那个
            } else {
                Arrays.sort(arr, begin + 5 * i, begin + i * 5 + 4);//排序非最后一组
                medians[indexOfMedians++] = arr[begin + i * 5 + 2];//当前组排序后的中间那个
            }
        }
        //对所有中值再排序取中值
        Arrays.sort(medians, 0, medians.length - 1);
        return medians[medians.length/2];
    }

附录:一份完整的快排代码

import java.util.Scanner;

public class T1快速排序 {
    public static void main(String[] args) {
        //输入"任意"长度的数组,以空格分隔,以回车结束输入
        Scanner sc = new Scanner(System.in);
        String[] str_array = sc.nextLine().toString().split(" ");
        int[] array = new int[str_array.length];
        for (int i = 0; i < array.length; i++) {
            array[i] = Integer.valueOf(str_array[i]);
        }

        //快速排序
        quickSort(array, 0, array.length - 1);
        //输出排序后的数组
        for (int i = 0; i < array.length; i++) {
            System.out.print(array[i] + " ");
        }
    }

    //快排函数
    static void quickSort(int[] arr, int begin, int end) {
        if (begin < end) {//分治区间大于2就继续划分
            //调用快排的内核函数partition确定主元位置,并将主元放到最终位置
            int pivot_loc = partition(arr, begin, end);
            //分治左区间
            quickSort(arr, begin, pivot_loc - 1);
            //分治右区间
            quickSort(arr, pivot_loc + 1, end);
        }
    }


    //双向扫描分区法(两指正等地位,向中间扫描)
    static int partition(int[] arr, int begin, int end) {
        getMid(arr, begin, end);
        int pivot = arr[begin];//确定主元为数组的第一个元素
        int left = begin + 1;//左侧指针
        int right = end;//右侧指针
        while (left <= right) {
            //注意加left<=right条件,防止左右指针咋在外部while的内部相交
            while (left <= right && arr[left] <= pivot) left++;
            while (left <= right && arr[right] > pivot) right--;
            //注意:交换左右指针所指元素时也要判定left<=right
            //因为在上面两个while中可能导致left和right错位
            if (left <= right) swap(arr, left, right);
        }
        //退出while表示已经相交,找到了pivot的最终位置:right位置(由arr[left]<=pivot决定)
        swap(arr, begin, right);
        return right;//返回位置
    }

    //交换数组中的两个元素
    static void swap(int[] arr, int index1, int index2) {
        int temp = arr[index1];
        arr[index1] = arr[index2];
        arr[index2] = temp;
    }

    //三点中值法
    static void getMid(int[] arr, int begin, int end) {
        int midIndex = (begin + end) / 2;//中间下标
        int midValueIndex = -1;//中值的下标

        if (arr[begin] <= arr[midIndex] && arr[begin] >= arr[end]) {
            midValueIndex = begin;
        } else if (arr[end] <= arr[midIndex] && arr[end] >= arr[begin]) {
            midValueIndex = end;
        } else {
            midValueIndex = midIndex;
        }
        //把中值放在数组的首位,便于取主元
        swap(arr, begin, midValueIndex);
    }
}
  • 1
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
摘要信息: 电子商城系统主要功能包括:用户注册、用户登录、用户查看商城商品、用户购买商品、用户查看购物车并且清空购物车、用户找回账户以及密码、管理员登录、管理员注册、管理员查看用户信息、管理员删除用户信息、管理员删除商品信息、管理员添加商品信息、管理员修改商品信息、退出登录等。 图形可视化界面运行下能够显示系统启动进度条,删除、添加、注册等相关操作时能够弹出窗口加以提示,退出登录或系统时能够做到单击确认退出按钮才退出登录或系统,能够对用户加以提示。 注册用户或是管理员、添加商品信息、购买商品、删除商品或是用户信息、修改商品信息等相关操作时能够判断有无该用户或是商品。 用户在购买商品的时候同时更新商品的库存数量,管理员删除管理员时可以做到同时删除用户的购买信息。 主要内容: 一、项目名称   基于JAVASE的电子商城系统 二、功能要求 1、用户注册 2、用户登录 3、用户查看商品列表 4、用户购买商品 5、用户查看购买商品列表 6、用户清空购物车 7、用户找回账户和密码 8、管理员登录 9、管理员注册 10、查看用户信息 11、查看商品信息 12、删除用户信息 13、删除商品信息 14、添加商品信息 15、修改商品信息 16、退出用户登录 17、退出管理员登录 18、退出商城 三、需求分析 该系统的用户是商城消费者和商城管理者,根据客户的要求,可以注册、登录、购物、查看购物车信息、找回账户和密码,管理员可以对商品信息进行增加、修改、删除操作,可以对用户进行查看和删除。 四、设计思想 1、在控制台与图形可视化界面下运行 2、使用Mysql数据库存取用户登录信息和商品信息 3、使用List存取商品购买信息 4、把程序分为多个类,多个类之间的互相调用。 5、用户或是管理员进行注册、登录时能够提供校验码。 6、用户或是管理员获取数据库信息时能够与数据库进行交互。 7、用户购物要做到简洁明了。 8、用户只需要身份证号码和邮箱地址就能找回账户和密码。 9、注册、删除、修改等操作要有信息提示。 10、用户、管理员进行操作时能够做到操作提示与用户名提示。 11、退出登录或是退出系统时能够做到让用户有所考虑。 五、具体实现   1、技术思路: 界面:基于控制台与图形可视化界面(Swing)实现用户的输入和输出。 程序流程:在函数中利用循环与递归 ,读取用户输入,调用模块实现各个子功能。 2、功能子模块划分: ① 注册模块 ② 登录模块 ③ 查看商品模块(查看商品列表,购买商品) ④ 查看购买商品信息 ⑤ 管理员登录(添加管理员信息,对商品信息进行查看、增加、 修改、删除,对用户信息进行查看、删除,删除用户信息 时能够做到同时删除消费记录) ⑥ 退出系统 六、运行截图 1、商城系统启动进度条(进度条能够做到动态加载): 2、商城主界面: 3、用户注册界面(填写注册信息不符合要求时能够弹窗提示、并 且判断用户名是否重复等): 4、用户登录界面(账户、密码不一致时能弹窗提示): 5、用户服务选择界面(能够提示用户名): 6、购买商品界面(能够判断输入的商品序号是否正确): 7、查看购物车界面(能够一键清空购物车): 8、查看商城商品信息界面: 9、管理员登录界面: 10、找回账户与密码界面: 11、管理员服务选择界面: 12、添加管理员界面: 13、查看用户信息界面: 14、删除用户信息界面: 15、删除商品信息界面: 16、添加商品信息界面: 17、修改商品信息界面: 18、退出账号、管理员登录、商城系统时要有提示:

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值