排序算法(Java语言)——快速排序

本文深入探讨了Java中快速排序算法的应用,包括其理论基础、关键步骤、性能优化策略以及常见问题解决方案。重点阐述了如何选择枢纽元、分割策略以及实现细节,以提高算法效率和稳定性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

    在Java中,快速排序用作基本类型的标准库排序。顾名思义,快速排序quicksort是实践中的一种快速的排序算法,在C++或Java基本类型的排序中特别有用。它的平均运行时间是O(NlogN)。该算法之所以特别快,主要是由于非常精练和高度优化的内部排序。它的最坏情形性能为O(N²),但经过稍许努力可使这种情况极难出现。

   虽然多年来快速排序算法曾被认为是理论上高度优化而在实践中不可能正确编程的一种算法,但是如今该算法简单易懂而且被证明是正确的,像归并排序一样,快速排序也是一种分治的递归算法。将数组S排序的基本算法由下列简单的四步组成:

  1. 如果S中元素个数是0或1,则返回。
  2. 取S中任一元素v,称之为枢纽元pivot
  3. 将S-|v|(即S中除v外其他元素划分成两个不相交的集合:S₁={x∈S-{v}|x≤v}和S₂={x∈S-{v}|x≥v}
  4. 返回{quciksort(S₁)后跟v,继而返回quciksort(S₂)
   由于对那些等于枢纽元的元素的处理上,第3步分割的描述不是唯一的,因此这就成了一种设计决策。一部分好的实现方法是将这种情况尽可能有效地处理。直观地勘,我们希望把等于枢纽元的大约一半的关键字分配到S₁中,而另一半分到S₂中,很像我们希望二叉查找树保持平衡的情形。
    如同归并排序那样,快速排序递归地解决两个子问题并需要线性的附加工作,不与归并排序不同,这两个子问题并不保证具有相等的大小,这是个潜在的隐患。快速排序更快的原因在于,第3步分割成两组实际上实在适当的位置进行并且非常有效,它的高效不仅可以弥补大小不等的递归调用的不足而且还能有所超出。
    迄今为止,对该算法的描述尚缺少很多细节,以下就来补充这些细节。

  • 选取枢纽元
    一种错误的方法:通常的,不安全的选择是将第一个元素用作枢纽元。如果输入是随机数,那么这是可以接受的,而如果输入是预排序或是反序的,那么这样的枢纽元就产生一个劣质的分割,因为所有的元素不是都被划入S₁就是S₂。更糟糕的是,这种情况毫无例外地发生在所有的递归调用中。实际上,如果第一个元素用作枢纽元而且输入是预先排序的,那么快速排序花费的时间将是二次的。而且,预排序的输入是相当常见的,因此,使用第一个元素作为枢纽元并不是好的方案。另一种想法是选取前两个互异的关键字中的较大者作为枢纽元,不过这和只选取第一个元素作为枢纽元具有相同的害处。不建议使用这两种选取枢纽元的策略。
    一种安全的做法:随机U型俺去枢纽元。一般来说这种策略非常安全,除非随机数发生器有问题,因为随机的枢纽元不可能总在接连不断地产生劣质的分割。另一方面,随机数的生成一半开销很大,根本减少不了算法其余部分的平均运行时间。
    三数中值分割法:一组N个数的中值(叫做中位数),第N/2(向上取整)个最大的数。枢纽元的最好的选择是数组的中值。不幸的是,这很难算出而且会明显减慢快速排序的速度。这样的中值的估计量可以通过随机选取三个元素并用它们的中值作为枢纽元而得到。事实上,随机性并没有多大的帮助,因此一般的做法是使用左端、右端和中心位置上的三个元素的中值作为枢纽元。

  • 分割策略
   分割是一种很容易出错或者低效的操作,但使用一种已知方法是安全的。该法的第一步是通过将枢纽元与最后的元素交换使得枢纽元离开要被分割的数据段。i从第一个元素开始而j从倒数第二个元素开始。在分割阶段要做的就是把所有小元素移到数组的左边而把所有大元素移动到数组的右边。当然,“小”和“大”是相对枢纽元而言的。另外我们必须考虑的一个重要的细节是如何处理那些等于枢纽元的元素。

    以下是快速排序的实现:
import java.util.Random;

public class QuickSortAlgorithm {
	private static final int CUTOFF = 3;

	/**
	 * 对数组执行快速排序算法
	 * 
	 * @param array
	 *            传入数组
	 */
	public static <AnyType extends Comparable<? super AnyType>> void quickSort(
			AnyType[] array) {
		quickSort(array, 0, array.length - 1);
	}

	/**
	 * 交换数组的两个对象
	 * 
	 * @param array
	 *            数组
	 * @param index1
	 *            下标一
	 * @param index2
	 *            下标二
	 */
	private static final void swapReferences(Object[] array, int index1,
			int index2) {
		Object tmp = array[index1];
		array[index1] = array[index2];
		array[index2] = tmp;
	}

	/**
	 * 三数中值分割法返回排序数组的枢纽元
	 * 
	 * @param array
	 *            三数中值分割法
	 * @param left
	 *            排序数组左端下标
	 * @param right
	 *            排序数组右端下标
	 * @return 一定范围的排序数组枢纽元
	 */
	private static <AnyType extends Comparable<? super AnyType>> AnyType median3(
			AnyType[] array, int left, int right) {
		int center = (left + right) / 2;
		if (array[center].compareTo(array[left]) < 0) {
			swapReferences(array, left, center);
		}
		if (array[right].compareTo(array[left]) < 0) {
			swapReferences(array, left, right);
		}
		if (array[right].compareTo(array[center]) < 0) {
			swapReferences(array, center, right);
		}
		// 把枢纽元放在right-1的位置
		swapReferences(array, center, right - 1);
		return array[right - 1];
	}

	/**
	 * 对数组的一定范围执行快速排序(递归)
	 * 
	 * @param array
	 *            数组
	 * @param left
	 *            左端下标
	 * @param right
	 *            右端下标
	 */
	private static <AnyType extends Comparable<? super AnyType>> void quickSort(
			AnyType[] array, int left, int right) {
		if (left + CUTOFF <= right) {
			AnyType pivot = median3(array, left, right);
			// 开始分割
			int i = left, j = right - 1;
			for (;;) {
				while (array[++i].compareTo(pivot) < 0) {
				}
				while (array[--j].compareTo(pivot) > 0) {
				}
				if (i < j) {
					swapReferences(array, i, j);
				} else {
					break;
				}
			}
			swapReferences(array, i, right - 1);// 恢复枢纽元
			quickSort(array, left, i - 1); // 小元素排序
			quickSort(array, i + 1, right); // 大元素排序
		} else {
			// 对子数组执行插入排序
			insertionSort(array, left, right);
		}
	}

	/**
	 * 插入排序
	 * 
	 * @param array
	 *            数组
	 * @param left
	 *            左下标
	 * @param right
	 *            右下标
	 */
	private static <AnyType extends Comparable<? super AnyType>> void insertionSort(
			AnyType[] array, int left, int right) {
		for (int p = left + 1; p <= right; p++) {
			AnyType tmp = array[p];
			int j;
			for (j = p; j > left && tmp.compareTo(array[j - 1]) < 0; j--) {
				array[j] = array[j - 1];
			}
			array[j] = tmp;
		}
	}

	public static void main(String[] args) {
		Integer[] array = new Integer[30];
		Random random = new Random();
		for (int i = 0; i < 30; i++) {
			array[i] = random.nextInt(30);
		}
		for (Integer integer : array) {
			System.out.print(integer + " ");
		}
		quickSort(array);
		System.out.println("\nAfter quick sort:");
		for (Integer integer : array) {
			System.out.print(integer + " ");
		}
	}
}

执行结果:
23 4 13 24 18 11 2 1 2 19 3 10 26 28 22 25 13 4 1 22 23 14 24 19 22 26 23 5 5 11 
After quick sort:
1 1 2 2 3 4 4 5 5 10 11 11 13 13 14 18 19 19 22 22 22 23 23 23 24 24 25 26 26 28 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值