算法设计: 二、递归与分治策略 (2. 快速排序、快速排序随机基准算法)—— java实现 - 算法分析

本文详细介绍了快速排序算法的基本思想,包括分解、递归求解和合并三个步骤,并给出了算法的Java实现。针对算法可能出现的最坏情况,即数组有序时效率降低,提出了随机选择基准的优化策略,以期望达到更均衡的划分,从而提高排序效率。随机基准算法通过在排序区间内随机选取基准,避免了因数组有序而导致的效率下降问题。
摘要由CSDN通过智能技术生成

分治法基本思想

将一个规模为 n 的问题分解为 k 个规模较小的问题,这些子问题相互独立且与原问题相同。递归地解这些子问题,然后将各子问题的解合并得到原问题的解。

1、快速排序算法

算法思想:

(1)分解 :以中轴元素 a[p] 将 a[p:r] 划分成3段 a[p:q-1]、a[q]、a[q+1:r] 。
(2)递归求解:递归调用对 a[p:q-1]、a[q+1:r] 进行排序。
(3)合并:由于对 a[p:q-1]、a[q+1:r] 的排序是就地进行的,因此不用再做合并操作。

快速排序算法 源码:
/**
 * 快速排序算法实现
 */
package quikesort;

public class QuikeSort {

	public static int[] array = {49, 38, 65, 97, 76, 13, 27, 49};
	
	public static void main(String[] args) {
		
		QuikeSort qs = new QuikeSort();
		qs.quikeSort(array, 0, array.length - 1);
		
		for(int i = 0; i < array.length; i++) {
			System.out.print(array[i]+" ");
		}
	}
	
	
	/**
	 * 分解数组,以基准(枢值)划分子数组,递归求解
	 * @param arr	待排序数组
	 * @param low	排序的起始位置
	 * @param high	排序的终止位置
	 */
	public void quikeSort(int[] arr, int low, int high) {
		
		if(low < high) {
			//基准位置
			int pivotLoc = partition(arr, low, high);
			//以基准划分为两个子数组,递归求解
			quikeSort(arr, low, pivotLoc - 1);
			quikeSort(arr, pivotLoc + 1, high);
		}
	}
	
	
	/**
	 * 一趟快速排序
	 * @return	返回当前基准位置
	 */
	public int partition(int[] arr, int low, int high) {
		//确定基准,以此划分数组
		int pivotkey = arr[low];
		
		//将小于基准的数交换到左边区域,将大于基准的数交换到右边区域
		while(low < high) {
			
			//从右往左,找到小于基准的数便停止,此时high指向该小于基准的数
			while (low < high && arr[high] >= pivotkey) {
				--high;
			}
			arr[low] = arr[high];
			
			//从左往右,找到大于基准的数便停止,此时low指向该大于基准的数
			while (low < high && arr[low] <= pivotkey) {
				++low;
			}
			arr[high] = arr[low];
		}
		//一趟交换结束,此时low==high
		//将基准放入交换的最后一个位置
		arr[low] = pivotkey;
		
		return low;
	}
}

算法分析
(1)问题分析

存在的问题:

在快速排序算法中,对于数组 a[p:r],选取 a[p] 作为基准(划分中轴)可以保证算法正常结束。如果选择 a[r] 作为划分的基准,且
a[r] 又是 a[p:r] 中的最大元素时,partition 划分函数的返回值为 q=r,这会使 quikeSort 函数陷入死循环。
·
原因分析:
因为上面的代码总是先从右往左与基准比较,若 a[r] 作为基准(pivotkey = a[r];),且 a[r] 是最大元素,则while之后 high==low 直接结束本趟排序,基准重新赋值给 a[r](a[r] = pivotkey;),导致数组无序的情况下,在 partition 函数中没有发生排序,返回的基准值任为原 a[r],导致无限循环。
·
解决办法:
由于是算法本身的限制,只能让其他方法避开此陷阱。当快速排序是先从右往左与基准比较时,避免以a[a.length-1]作为基准;当先从左往右与基准比较时,避免以a[0]作为基准。

(2)效率分析

最好时间复杂度:O(logn)
最坏时间复杂度:O(n2)
平均时间复杂度:O(logn)
空间复杂度:O(logn)
·
最好情况:每次划分所取的基准都恰好为当前序列的中值,即n/2,此时取得最好时间复杂度。
·
可知快速排序算法的性能取决于划分的对称性

(3)效率优化

当排序的 序列很大 且 较为有序 时,可以进行一定的效率优化。注意仅在当前情况下可提高算法效率。

根据 快速排序算法的性能取决于划分的对称性,修改基准的选择方法,使其选到中值或者接近中值,从而提高快速排序算法的效率。

采用随机基准选择策略:

由于未排序的数组中值未知,只能用随机选择方式增大每个元素作为基准的概率,而不是一味的选取相对固定位置上的元素(如a[p]、a[r]),尤其避免了数组本身可能是相对比较有序的情况带来的划分不均。而随机选择可以期望划分是比较对称的。

2、快速排序随机基准算法

快速排序随机基准算法 源码:
/**
 * 快速排序的随机基准算法
 */
package randomquikesort;

import java.util.Random;

public class RandomQuikeSort {
public static int[] array = {49, 38, 65, 97, 76, 13, 27, 49};
	
	public static void main(String[] args) {
		
		RandomQuikeSort qs = new RandomQuikeSort();
		qs.randomQuikeSort(array, 0, array.length - 1);
		
		for(int i = 0; i < array.length; i++) {
			System.out.print(array[i]+" ");
		}
	}
	
	
	/**
	 * 分解数组,以基准(枢值)划分子数组,递归求解
	 * @param arr	待排序数组
	 * @param low	排序的起始位置
	 * @param high	排序的终止位置
	 */
	public void randomQuikeSort(int[] arr, int low, int high) {
		
		if(low < high) {
			//基准位置
			int pivotLoc = randomPartition(arr, low, high);
			//以基准划分为两个子数组,递归求解
			randomQuikeSort(arr, low, pivotLoc - 1);
			randomQuikeSort(arr, pivotLoc + 1, high);
		}
	}
	
	
	/**
	 * 一趟快速排序
	 * @return	返回当前基准位置
	 */
	public int Partition(int[] arr, int low, int high) {
		//确定基准,以此划分数组
		int pivotkey = arr[low];
		
		//将小于基准的数交换到左边区域,将大于基准的数交换到右边区域
		while(low < high) {
			
			//从右往左,找到小于基准的数便停止,此时high指向该小于基准的数
			while (low < high && arr[high] >= pivotkey) {
				--high;
			}
			arr[low] = arr[high];
			
			//从左往右,找到大于基准的数便停止,此时low指向该大于基准的数
			while (low < high && arr[low] <= pivotkey) {
				++low;
			}
			arr[high] = arr[low];
		}
		//一趟交换结束,此时low==high
		//将基准放入交换的最后一个位置
		arr[low] = pivotkey;
		
		return low;
	}
	
	/**
	 * 随机获取基准
	 * @return	返回调用Partition:返回当前基准位置
	 */
	public int randomPartition(int[] arr, int low, int high) {
		
		Random ran = new Random();
		//在当前数组中产生随机基准,且摒弃最后一个元素
		int randomPivotkey = ran.nextInt(high - low) + low;
		//将随机取得的基准交换到arr[low]以复用原Partition函数
		swap(arr, low, randomPivotkey);
		
		return Partition(arr, low, high);
	}
	
	
	/**
	 * 交换元素
	 */
	public void swap(int[] arr, int x, int y) {
		
		int tmp = arr[x];
		arr[x] = arr[y];
		arr[y] = tmp;
	}
}

效率测试

测试符合上述理论,这里不再给出测试过程及结果。

具体测试过程及结果请转:
快速排序算法优化(快速排序随机基准算法)—— 测试过程及结果分析

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Whitemeen太白

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值