zoukankan      html  css  js  c++  java
  • 数据结构与算法排序(五)快速排序(Quick Sort)

    摘要

    快速排序和归并排序有一些相似的地方,就是在中间位置拆分成两部分,分别做处理。

    这里也是用到递归思想,但是与归并排序的先拆分再排序处理的思想不同,快速排序是先处理排序,然后再拆分。

    本质

    每一次确定一个轴点元素,然后和序列中的其他元素比较,放在元素的左右任何位置位置,完成之后,这个轴点元素的位置就明确了。

    理解这个快速应该就是去搞定一个元素的位置,不管其他元素元素位置,就少了比较处理(因为绝情,所以快吗?- 我的理解

    逻辑

    1. 序列中选择一个元素,设置为轴点元素(pivot)
    2. 利用 pivot将序列分割成两个子序列
      • 把小于 pivot 的元素放在序列的左侧
      • 把大于 pivot 的元素放在序列的右侧
      • 把等于 pivot 的元素可以放在左侧或者右侧都可
    3. 将子序列继续进行如上 1 2 的操作,直到无法再进行分割为止

    流程

    1. 以 0 坐标为轴点,随机获取轴点元素,和 0 位置元素更换
    2. 序列头尾元素交替和轴点元素比较和调整替换,将小于轴点元素放在轴点坐标左侧,大于轴点元素放在轴点坐标右侧。
    3. 以轴点元素为中线,分割成两个子序列,继续 1 2 操作(递归性质)
    4. 直到首尾坐标相等(即序列中只有一个元素)为止

    实现

    将序列分为子序列的递归方法。凡是递归方法,必须要有终止递归的判断条件,否则会造成死循环,需要特别注意

    	/**
    	 * 在 [begin, end) 范围内进行比较
    	 * @param begin
    	 * @param end
    	 */
    	private void sort(int begin, int end) {
    		if ((end - begin) < 2) return;
    		
    		int mid = pivotIndex(begin, end);
    		// 分割为两个子序列
    		sort(begin, mid);
    		sort(mid+1, end);
    	}
    

    以轴点坐标分割线,调整序列中的的元素,并返回新的轴点坐标。当已经比较交换完成轴点坐标时,一定要将轴点元素赋值到新的轴点坐标中

    这里的代码有一个巧妙的点,就是比较并交换头尾位置

    这里用大循环嵌套两个小循环的方式,达到头部和尾部交替和轴点元素比较并交换位置,这个方式非常巧妙。

    首先咱要明确目的,就是序列中小于轴点的元素放在它的左边,大于轴点的元素放在它的右边。在不增加额外内存空间的情况下,就可以用这样的方式去处理。

    具体的逻辑可以着重看一下代码,这里说几个需要注意的点

    1. 这里的比较是首尾交替比较,用三个 while 循环达到这样的目的
    2. begin 和 end 就相当于两个指针,通过交换之后就变换比较的方向,这一点非常巧妙。
    	/**
    	 * 在 [begin, end) 返回内构造轴点坐标
    	 * @param begin
    	 * @param end
    	 * @return
    	 */
    	private int pivotIndex(int begin, int end) {
    		// 通过随机数获取坐标(begin 和 end 范围内),和 begin 坐标元素进行交换,避免出现 2^n
    		swap(begin, begin + (int)(Math.random()*(end - begin)));
    		// 轴点元素
    		E pivot = array[begin];
    		
    		// end 指向最后一个元素
    		end--;
    		
    		// 头部尾部交替比较
    		while (begin < end) {
    			// 尾部进行比较
    			while (begin < end) {
    				if (cmp(pivot, array[end]) < 0) {
    					end--;
    				} else {
    					array[begin] = array[end];
    					begin++;
    					break;
    				}
    			}
    			
    			// 头部比较
    			while (begin < end) {
    				if (cmp(pivot, array[begin]) > 0) {
    					begin ++;
    				} else {
    					array[end] = array[begin];
    					end--;
    					break;
    				}
    			}
    		}
    		
    		// 将轴点元素放入最终的位置
    		array[begin] = pivot;
    		return begin;
    	}
    

    问题

    Q:为什么获取轴点坐标开始时,要获取序列中的随机一个元素和轴点坐标元素交换?

    A:防止轴点左右元素数量极端不均匀

    Q:序列的边界是怎么设置的,为什么要这样处理?

    A:序列边界是 [begin, end),左闭右开,这样设置方便能获取序列的长度,最后一个坐标也可以很好获得

    进阶

    随机选择轴点元素。这是防止例如序列是从大到小的有序序列这种极端情况出现。

    时间和空间复杂度

    • 最好、平均时间复杂度:O(nlogn)
    • 最坏时间复杂度:O(n^2)
    • 空间复杂度:O(logn)
    • 属于不稳定排序
  • 相关阅读:
    Nginx入门使用
    下载pcre-8.35.tar.gz
    后端传到前端时间问题
    Layui弹出层分割线
    CSS动画实例:图文切换
    JavaScript小游戏实例:简单的键盘练习
    JavaScript小游戏实例:统一着色
    JavaScript动画实例:炸开的小球
    JavaScript动画实例:烟花绽放迎新年
    JavaScript动画实例:圆点的衍生
  • 原文地址:https://www.cnblogs.com/shsuper/p/15125643.html
Copyright © 2011-2022 走看看