快速排序(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.优缺点

  • 优点

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

  • 缺点

不稳定,不适合对象排序

posted @ 2020-01-12 20:43  flunggg  阅读(163)  评论(0编辑  收藏  举报