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)
    • 属于不稳定排序
  • 相关阅读:
    1063. Set Similarity
    A1047. Student List for Course
    A1039. Course List for Student
    最大公约数、素数、分数运算、超长整数计算总结
    A1024. Palindromic Number
    A1023. Have Fun with Numbers
    A1059. Prime Factors
    A1096. Consecutive Factors
    A1078. Hashing
    A1015. Reversible Primes
  • 原文地址:https://www.cnblogs.com/shsuper/p/15125643.html
Copyright © 2011-2022 走看看