数据结构排序算法之快速排序法

算法描述

快速排序:

快速排序由C. A. R. Hoare在1962年提出。它的基本思想是:通过一趟排序将要排序的数据分割成独立的两部分,其中一部分的所有数据都比另外一部分的所有数据都要小,然后再按此方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行,以此达到整个数据变成有序序列。

方法

Divide:分治,将数组分成三部分:A[p…r]–>A[p…q-1]<=A[q]<A[q+1…r]
Conquer:递归地对A[p…q-1],A[q]<A[q+1…r]进行快速排序,直到区间长度为0
Combine:空操作,因为每个子数组上进行的是原址重排。

伪代码

QucikSort(A,p,r){
if(p<r)
	q=Partition(A,p,r)
	QuickSort(A,p,q-1)
	QuickSort(A,q+1,r)
}
//算法的关键部分是Partition,它实现了堆子数组的原址重排
Partition(A,p,r){
	x=A[r]//选取最后一个元素作为基准
	i = p-1//i指向最左边的位置-1
	for j=p to r-1 //从最左边到最右边遍历
		if A[j]<=x //如果比基准元素x小
			i=i+1 //向右移动指针
			exchange(A[i],A[j])//交换
			//endif
	//endfor
	//除了最后一个元素,已经将子数组分成了比x大的和比x小的
	exchange(A[i+1],A[r])//把基准元素x放到中间
	
	
}

图解

在这里插入图片描述
在这里插入图片描述

复杂度分析

快速排序的运行时间依赖于划分是否平衡,而是否平衡又依赖于用于划分的元素。如果划分是平衡的,那么快排算法性能与归并排序相同。如果不平衡,快排的行能就接近于插入排序了。

最坏情况

如果每层递归上,划分都是最大不平衡(分成了n-1个元素和0个元素), T ( 0 ) = Θ ( 1 ) T(0)=\Theta(1) T(0)=Θ(1)算法的时间复杂度是: T ( n ) = T ( n − 1 ) + T ( 0 ) + Θ ( n ) = T ( n − 1 ) + Θ ( n ) T(n)= T(n-1)+T(0)+\Theta(n)=T(n-1)+\Theta(n) T(n)=T(n1)+T(0)+Θ(n)=T(n1)+Θ(n)
利用代入法可解得 T ( n ) = Θ ( n 2 ) T(n)=\Theta(n^2) T(n)=Θ(n2)
此外,如果输入数组已经完全有序了,时间复杂度也是O(n2)。

最好情况

在可能的最平衡的划分中,Partition操作得到的两个子问题规模都不大于n/2,其中一个是n/2,另一个是n/2-1,算法的时间复杂度是: T ( n ) = 2 T ( n / 2 ) + Θ ( n ) T(n)=2 T(n/2)+\Theta(n) T(n)=2T(n/2)+Θ(n)
利用代入法可解得 T ( n ) = Θ ( n l g n ) T(n)=\Theta(nlgn) T(n)=Θ(nlgn)
快排的平均复杂度更接近于其最好运情况。

实现

	public void QuickSort(int a[], int p, int r) {
		if (p < r) {
			int q = Partition(a, p, r);//选出基准元素
			QuickSort(a, p, q - 1);//基准元素左边
			QuickSort(a, q + 1, r);//基准元素右边
		}

	}

	private int Partition(int[] a, int p, int r) {
		// TODO Auto-generated method stub
		int base = a[r];// 基准元素选最后一个
		int i = p - 1;
		for (int j = p; j <= r - 1; j++) {
			if (a[j] <= base) {// 小于基准元素
				i++;
				swap(a, i, j);
			}
		}
		// 划分完成,将基准元素归位
		swap(a, i + 1, r);
		return i + 1;
	}

	private void swap(int a[], int i, int j) {
		// TODO Auto-generated method stub
		int tmp = a[i];
		a[i] = a[j];
		a[j] = tmp;
	}
	public static void main(String[] args) {
		int a[] = { 2, 8, 7, 1, 3, 5, 6, 4 };
		for (int i : a)
			System.out.print(i + " ");
		Sort st = new Sort();
		st.QuickSort(a, 0, 7);
			System.out.print("\n排序后 ");
		for (int i : a)
			System.out.print(i + " ");
}

运行结果
在这里插入图片描述

随机化版本的快排

上面版本的快排在选取主元的时候,每次都选取最右边的元素。当序列为有序时,会发现划分出来的两个子序列一个里面没有元素,而另一个则只比原来少一个元素。为了避免这种极端的情况,我们引入一个随机化量来破坏这种有序状态。
在随机化的快排里面,选取a[p..r]中的随机一个元素作为主元,然后再进行划分,就可以得到一个平衡的划分。
实现起来其实只需要对上面的代码做小小的修改就可以了。

代码实现

/**
	 * 随机化的快速排序
	 * 
	 * @param a
	 * @param p
	 * @param r
	 */
	public void RandomQuickSort(int a[], int p, int r) {
		if (p < r) {
			int q = RandomPartition(a, p, r);//这里改为调用随机划分方法
			QuickSort(a, p, q - 1);
			QuickSort(a, q + 1, r);
		}
	}

/**
	 * 每次选一个随机元作为划分元
	 * 
	 * @param a
	 * @param p
	 * @param r
	 * @return
	 */
	private int RandomPartition(int[] a, int p, int r) {
		// TODO Auto-generated method stub
		int rand = random.nextInt(r - p) + p;
//		System.out.println(rand);
		swap(a, rand, r);
		return Partition(a, p, r);
	}

测试

public static void main(String[] args) {

		Random rand = new Random();
		// 10000个数 性能比较
		int a[] = new int[10000];
		int a2[] = new int[10000];//注意这里要额外声明一个a2,防止测试的时候出现再去排序已经排好序的数组
		// 用随机数初始化
		for (int i = 0; i < 10000; i++) {
			a[i] = rand.nextInt(Integer.MAX_VALUE);
			a2[i] = a[i];
		}
		long startTime = System.currentTimeMillis();
		Sort st = new Sort();
		st.RandomQuickSort(a, 0, 9999);
		long endTime = System.currentTimeMillis();
		System.out.print("10000个随机数,随机化快排所用时间是:" + (endTime - startTime) + "ms\n");
		startTime = System.currentTimeMillis();
		st.QuickSort(a2, 0, 9999);
		endTime = System.currentTimeMillis();
		System.out.print("10000个随机数,普通快排所用排序所用时间是:" + (endTime - startTime) + "ms\n");

		// 100000个数比较
		int b[] = new int[100000];
		int b2[] = new int[100000];
		// 用随机数初始化
		for (int i = 0; i < 100000; i++) {
			b[i] = rand.nextInt(Integer.MAX_VALUE);
			b2[i] = b[i];
		}

		startTime = System.currentTimeMillis();
		st.RandomQuickSort(b, 0, 99999);
		endTime = System.currentTimeMillis();
		System.out.print("100000个随机数,随机化快排所用时间是:" + (endTime - startTime) + "ms\n");

		try {
			startTime = System.currentTimeMillis();
			st.QuickSort(b2, 0, 99999);
			endTime = System.currentTimeMillis();
			System.out.print("100000个随机数,普通快排所用时间是:" + (endTime - startTime) + "ms\n");
		} catch (StackOverflowError e) {
			System.out.print("100000个随机数,普通快排所用排序StackOverFlowError\n");
		}
//		
		// 10000有序数列 两种性能比较
		int c[] = new int[10000];
		int c2[] = new int[10000];
		// 初始化
		for (int i = 0; i < 10000; i++)
			c2[i] = c[i] = 10000 - i;

		startTime = System.currentTimeMillis();
		st.RandomQuickSort(c, 0, 9999);
		endTime = System.currentTimeMillis();
		System.out.print("10000个有序数,随机化快排所用时间是:" + (endTime - startTime) + "ms\n");

		startTime = System.currentTimeMillis();
		st.QuickSort(c2, 0, 9999);
		endTime = System.currentTimeMillis();
		System.out.print("10000个有序数,普通快排所用时间是:" + (endTime - startTime) + "ms\n");
}

结果

在这里插入图片描述
可以看到1万个数,10万个数的时候,两种方法相差并不多,但是当输入数组是有序的时候,随机化快排的时间就比普通快排要用时少了,其性能也体现出来了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值