快速排序及优化详解

  本系列文章共有十一篇:
    冒泡排序及优化详解
    快速排序及优化详解
    插入排序及优化详解
    希尔排序及优化详解
    选择排序及优化详解
    归并排序及优化详解
    堆排序详解
    计数排序及优化详解
    桶排序详解
    基数排序及优化详解
    十大排序算法的比较与性能分析

一、快速排序基础

  快速排序也是一种较为基础的排序算法,其效率比上篇文章介绍的冒泡排序算法有大幅提升。因为使用冒泡排序时,一趟只能选出一个最值,有n个元素最多就要执行n - 1趟比较。而使用快速排序时,一次可以将所有元素按大小分成两堆,也就是平均情况下需要logn轮就可以完成排序。
  快速排序的思想是:每趟排序时选出一个基准值,然后将所有元素与该基准值比较,并按大小分成左右两堆,然后递归执行该过程,直到所有元素都完成排序。
  快速排序的步骤如下:
   1>先从数列中取出一个数作为基准数。
   2>分区过程,将比这个数大的数全放到它的右边,小于或等于它的数全放到它的左边。
   3>再对左右区间重复第二步,直到各区间只有一个数。

1.1 排序过程图示

  假如有[4,7,2,8,1]五个元素,下面就以常见的“挖坑法”来演示一次快速排序的过程:
  首先选定基准元素key,并记住这个位置index,这个位置相当于一个“”。并且设置两个指针left和right,指向数列的最左和最右两个元素:
      
  接下来,从right指针开始,把指针所指向的元素和基准元素做比较。如果比key大,则right指针向左移动;如果比key小,则把right所指向的元素填入坑中。在当前数组元素中,1<4,所以把1填入基准元素所在位置,也就是坑的位置。这时候,元素1本来所在的位置成为了新的坑。同时,left向右移动一位:
      
  接下来,我们切换到left指针进行比较。如果left指向的元素小于key,则left指针向右移动;如果元素大于key,则把left指向的元素填入坑中。在当前数列中,7>4,所以把7填入index的位置。这时候元素7本来的位置成为了新的坑。同时,right向左移动一位。
      
  继续上面的过程,8>4,元素位置不变,right左移:
      
  2<4,用2来填坑,left右移,切换到left:
      
  当left和right指针重合时,把之前的key元素,也就是4放到index的位置。此时数列左边的元素都小于4,数列右边的元素都大于4,这一轮交换终告结束:
      

1.2 快速排序实现

  用代码来实现上述过程,示例如下:

	static void quickSort(int[] arr, int startIndex, int endIndex) {
		/*当startIndex大于等于endIndex时,不再递归*/
		if (startIndex >= endIndex) {
		    return;
		}
		/*得到基准元素位置*/
		int keyIndex = divide(arr, startIndex, endIndex);
		/*递归处理基准的左右两部分*/
		quickSort(arr, startIndex, keyIndex - 1);
		quickSort(arr, keyIndex + 1, endIndex);
    }
	
	static int divide(int[] arr, int startIndex, int endIndex) {
		/*取第一个位置的元素作为基准元素*/
		int key = arr[startIndex];
		int left = startIndex;
		int right = endIndex;
		/*坑的位置,初始等于key的位置*/
		int index = startIndex;
		
		/*当right大于等于left时,执行循环*/
		while (right >= left){
		   /*right指针从右向左进行比较*/
		   while (right >= left) {
		      if (arr[right] < key) {
		    	  /*最右边的元素覆盖原来坑中的值*/
		          arr[left] = arr[right];
		          /*坑的位置改变,变成最右边元素对应的索引*/
		          index = right;
		          /*left索引右移,因为原来的值已被right索引的值所覆盖*/
	              left++;
		          break;
		      }
		      /*最右边的元素参与比较,无论是否参与与坑中元素的交换,都左移,不再参与下一轮的比较*/
		      right--;
		   }
		   
		   /*left指针从左向右进行比较*/
		   while (right >= left) {
		      if (arr[left] > key) {
		         arr[right] = arr[left];
		         index = left;
		         right--;
		         break;
		      }
		      left++;
		   }
		}
		
		/*将基准元素放在index位置,也就是大小元素放在其前后的中间位置*/
		arr[index] = key;
		return index;
	}

二、快速排序优化

2.1 三数取中

  该方法指的是选取基准值时,不再取固定位置(如第一个元素、最后一个元素)的值,因为这种固定取值的方式在面对随机输入的数组时,效率是非常高的。但是一旦输入数据是有序的,使用固定位置取值,效率就会非常低。因此此时引入了三数取中,即在数组中随机选出三个元素,然后取三者的中间值做为基准值。

2.2 插入排序

  当待排序序列的长度分割到一定大小(如 < 10)后,使用插入排序。

2.3 相等元素聚集

  在一次分割结束后,可以把与Key相等的元素聚在一起,继续下次分割时,不用再对与key相等元素分割。

三、稳定性、复杂度和适用场景

3.1 稳定性

  在使用快速排序时,每次元素分堆都要选择基准因子。此时,基准因子两边都有可能出现和基准因子相同的元素,如序列[1,3,2,4,3,4,6,3],如果选择了array[4]作为基准因子,那么array[1]和array[7]势必会被分到基准因子的同一侧,序列的稳定性被破坏。所以,快速排序是一种不稳定的排序算法。

3.2 时间复杂度

  快速排序的时间复杂度是O(nlogn)。

3.3 适用场景

  快速排序的适用场景是:待排序序列元素较多,并且元素较无序。

猜你喜欢

转载自blog.csdn.net/m0_37741420/article/details/106984995
今日推荐