zoukankan      html  css  js  c++  java
  • 快速排序(Quick Sort)

    1.基本思想

    选定一个基准数,通过一趟排序将要排序的数据分割成独立的两部分,其中左部分的数据比基准数小,右部分的数据比基准数大,然后再按此方法先选取基准数,再对这两部分数据分别进行快速排序,整个排序过程可以递归进行,以此达到整个数据变成有序序列。

    简单来讲就是:每次排序时选取一个基准数,将小于等于基准数的数全部放在基准数的左边,将大于等于基准点的数全部放在基准数的右边,待基准数归位,再继续拆分成两部分继续执行上述操作。

    2.算法设计

    现在对"6 1 2 7 9 3 4 5 10 8"这10个数进行从小到大排序

    1. 首先进行选取基准数,为了方便先选取第一个数6作为基准数。接下来,目的是把这个序列中所有比基准数小的放在基准数左边,所有比基准数大的放在基准数右边。
    2. 那么我们设置两个指针变量分别为i、j,i指向序列的最左边,j指向序列的最右边,我们把i和j称为哨兵i和哨兵j。然后从两端开始"探测"。先从右往左找一个小于6的数,再从左往右找一个大于6的数,然后交换它们。
      1
    3. 因为此处设置的基准数时最左边的数,所以需要让哨兵j先出发(先想想为什么)。哨兵j一步一步向左移动(j–),直到找到第一个小于基准数6的数,就停止。接着哨兵i出动,一步一步向右移动(i++),直到找到第一个大于基准数6的数就停下,这个过程如下:
      2
      现在交换哨兵i和哨兵j所指的元素的值,如下:
      3
    4. 第一次交换结束。接下来哨兵j继续向左移动,寻找比基准数6小的数,找到了4,就停止。而哨兵i也继续向右,找到了9,停止。
      4
      进行交换操作。
      5
    5. 第二次交换结束,"探测"继续。哨兵j继续向左移动,发现了3满足要求就停下来。哨兵i也继续向右移动,此时发现哨兵i和哨兵j相遇,它们都移动到3面前。说明此时"探测"结束(已经不需要再继续移动,因为哨兵i已经处理完左部分,哨兵j已经处理完右部分),将基准数6和3交换(这一过程可以回答为什么要哨兵j先走,试试看如果是哨兵i先走会出现什么结果)。如下:
      6

    第一轮"探测"结束。此时以6为分界点,6左边的数都小于等于6,6右边的数都大于等于6。回顾第一轮的过程,哨兵j的使命就是要找小于基准数的数,哨兵i的使命是找出大于基准数的数,直到哨兵i和哨兵j碰头为止。

    现在基准数6已经归位。以6为分界点把该序列分割成两部分,左边的序列是"3 1 2 5 4",右边的序列是"9 7 10 8"。接下来还需要继续处理这两个序列,目前它们的顺序还是很混乱。按照刚刚的方法,比如先处理左序列。

    1. 选取第一个数3作为基准数。现在将这个序列以3为基准数进行调整,使得3左边的数都小于3,3右边的数都大于3。得出的结果序列是:2 1 3 5 4。
    2. 现在3已经归位。接下来又以3为分界点,把该序列拆分两部分,分别处理3左边的序列"2 1"和3右边的序列"5 4"。对于"2 1"以2为基准数进行调整,处理完毕之后的序列为"1 2",到此2已经归位。而序列"1"只有一个数,就不用进行任何处理。到此对于序列"2 1"已经全部处理完毕,得到的序列是"1 2"。序列"5 4"的处理也仿照此方法,最后得到的序列:1 2 3 4 5 6 9 7 10 8
    3. 对于序列"9 7 10 8"也模拟刚才的过程,直到不可拆分出新的子序列为止。最终得到这样的序列:1 2 3 4 5 6 7 8 9 10。

    到此,排序已经全部完毕。快速排序的每一轮处理其实就是将这一轮的基准数归位,直到所有的数都归位为止。

    3.代码

    public class QuickSort {
    	
    	static void quicksort(int[] a, int left, int right) {
    		int i, j, t, temp; // temp来存储基准数
    		if(left > right) {
    			return ;
    		}
    		
    		// 选择基准数
    		temp = a[left];
    		i = left;
    		j = right;
    		// i和j还没有相遇
    		while(i != j) {
    			// 必须先从右开始,即j开始出发,寻找一个小于等于基准数的数
    			while(a[j] >= temp && j > i) {
    				j--;
    			}
    			// 然后从左出发,寻找一个大于等于基准数的数
    			while(a[i] <= temp && j > i) {
    				i++;
    			}
    			// 交换两个数在数组中的位置
    			if(j > i) { // 当j和i还没有相遇时
    				t = a[j];
    				a[j] = a[i];
    				a[i] = t;
    			}
    		}
    		
    		// 最终将基准数归位
    		a[left] = a[i];
    		a[i] = temp;
    		
    		// 继续处理左部分的
    		quicksort(a, left, i-1);
    		// 处理右部分的
    		quicksort(a, i+1, right);
    	}
    	
    	public static void main(String[] args) {
    		int[] a = {6,1,2,7,9,3,4,5,10,8};
    		quicksort(a, 0, a.length-1);
    		System.out.println(Arrays.toString(a));
    	}
    }
    

    4.复杂度

    • 时间复杂度

    快速排序的最差时间复杂度跟冒泡排序是一样的,都是O(N^2),它的平均时间复杂度为O(NlogN)。

    • 空间复杂度

    可以看出就是待排序的数的数量,即O(N)。

    5.优缺点

    • 优点

    快速排序速度很快,相比于冒泡排序,每次交换都是跳跃式的交换。

    • 缺点

    不稳定,不适合对象排序

  • 相关阅读:
    快乐的深圳之旅
    编码和字体[zz]
    USB转串口芯片几点总结有疑问
    ANSI/UTF8/UCS2(UTF16),以及回车换行[zz]
    详细介绍四线电阻触摸屏的工作原理[zz]
    无字库12864液晶的驱动方法[zz]
    字符集和字符编码(Charset & Encoding)[zz]
    搭建CodeBlocks+wxWidgets可视化编程环境(Windows)
    wxWidgets初始化分析应用定义和初始化
    开发CodeBlocks插件(1)入门篇
  • 原文地址:https://www.cnblogs.com/flunggg/p/12184655.html
Copyright © 2011-2022 走看看