[编程珠玑]-第十一章:快速排序及第k小元素

源于编程珠玑-第二版

这几天在通过编程珠玑回顾复习一些算法相关的基础知识,虽说做不到像一些算法高手深入剖析各式优化的特点,但通过一些问题和例子看看也是能够温故知新的。重在学习、收获,不积跬步无以至千里。

先前通过算法导论的一些相关章节学习,写了点笔记,不过由于现在并没有在身边,这篇文章就简单记录一下编程珠玑中提到的快速排序内容。另外还有一个关于“O(n)时间内从数组中查找第k小元素”问题。

-------------------------------

其实本篇文章的重点是想引出“O(n)时间内从数组中查找第k小元素”问题和网上无意间看到的几篇详细讲解的文章

1.快速排序。(算是前引)

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

简单地说就是从数组X[0...n-1]中选择一个数作为基准(枢纽),通过将数组中元素与之相比较,移动位置使整个数组划分成两部分--X[L...M-1] 中所有元素都小于X[M], X[M+1....U]中所有元素都大于X[M]。但两部分内部不一定是有序的。然后依次递归进行。最终使整个数组有序。(好像并不符合“简单地说”这4个字....)

如图:



通过将枢纽元素放置数组尾部,然后前面的元素依次与之比较,i最初位于p-1的位置上,j从i+1的位置开始。

检查元素时若X[j]>t则j继续后移,而如果X[j]<t 则将i+1 并与X[j]互换元素值,即将此数归到小于t的那部分中。

最后将X[r]的元素值换到中间去。

public static 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);
	}
}

public static int PARTITION(int A[],int p,int r){
	int temp=0;
	int i=p-1;
	for (int j = p; j <= r-1; j++) {
		if(A[j]<=A[r]){
			i++;
			temp=A[i];
			A[i]=A[j];
			A[j]=temp;
		}
	}
	 temp=A[i+1];
	A[i+1]=A[r];
	A[r]=temp;
	return i+1;
}
//QUICKSORT(A, 0, A.length-1);

----------上述方式每次将数组尾部元素作为枢纽进行排序

快速排序的一个重要标准即是对枢纽元素的选择。

2.快速排序(随机枢纽)

so.可以改进为选择随机一个元素放到数组尾部,作为枢纽。

public static int RandomPartition(int A[],int p,int r){
	if(r>p){
		int i=random.nextInt(r-p)+p;
		int temp=A[r];
		A[r]=A[i];
		A[i]=temp;
	}
	return PARTITION(A, p, r);
}

---------------回想一下,在数据结构专业课上接触快速排序的时候,似乎是由两个变量从左右两方依次向中间扫描来比较元素的?

3.快速排序(双向划分)


public static int PARTITIONd(int A[],int p,int r){
	int temp=0;
	int i=p-1,j=r;
	 while(true){
		do {
			i++;
		} while (i<=r-1&&A[i]<A[r]);
		
		do {
			j--;
		} while (A[j]>A[r]);
		
		if(i>j)
			break;
		temp=A[i];
		A[i]=A[j];
		A[j]=temp;
	 }
		temp=A[i];
		A[i]=A[r];
		A[r]=temp;
	return i;
}
(同样可以加上随机枢纽)

快速排序在元素较多的时候可以起到比较好的效果,当数组长度比较小时,利用插入排序等会更加有效。因此可以结合两者形成混合排序算法,当数组长度小于一定值时,转而使用其他排序来进行。

-------------------------------------------------------------------

编程珠玑第二版十一章习题。

在O(n)时间内从数组X[0....n-1]中找出第k个最小的元素。

方法有很多,第一秒最直接的想法就是排序后输出第k个元素。

但是,如何能做到在O(n)的时间内输出该元素呢。

计数排序利用元素值作为数组小标,在遍历一次之后将存在的数的元素值标为1,可以简单地输出第k小的元素,并且如果存在多个相等的k也没有关系,如 X[1]=3 X[2]=3,要查找第4小的元素,k=4, 减一下就可以了。

相似地也可以利用位向量(编程珠玑第一章有提到),不过位向量表示的话,无法解决重复元素问题了。

较好的方式可以利用快速排序划分的思想。

/*
* 从数组X[0...n-1]中找出第k个最小元素
* 思路:利用快速排序中的划分思想
* 通过划分左右子数组,若Len(X[L..t])==k个 则即X[t]为所求答案,
* Len(X[L..t])>k个 则对X[L..t]继续排序划分 
* 若Len(X[L..t])<k个 则第k小元素在X[t+1..U]范围中,对右边继续进行排序划分
* 每次通过判断舍去一半的子数组。
* 直到求得结果。
* (第k小元素,在数组中下标0开始,即为第k-1位置上的数)
* */

下述的是使用随机枢纽的方案,不过这可能还无法满足题中的O(n)时间,书上有提到利用中位数。

public static void MinK(int[] A,int L,int U,int k){
	if(L<U){
		int q=RandomPartition(A, L, U);
		if(q==k)
			System.out.println(A[q]);
		else if(q>k)
			MinK(A,L,q-1,k);
		else if (q<k) {
			MinK(A,q+1,U,k);
		}
	}
}

public static Random random=new Random();
public static int RandomPartition(int A[],int p,int r){
	if(r>p){
		int i=random.nextInt(r-p)+p;
		int temp=A[r];
		A[r]=A[i];
		A[i]=temp;
	}
	return PARTITIONk(A, p, r);
}
public static int PARTITIONk(int A[],int p,int r){
	int temp=0;
	int i=p-1,j=r;
	 while(true){
		do {
			i++;
		} while (i<=r-1&&A[i]<A[r]);
		
		do {
			j--;
		} while (A[j]>A[r]);
		
		if(i>j)
			break;
		temp=A[i];
		A[i]=A[j];
		A[j]=temp;
	 }
		temp=A[i];
		A[i]=A[r];
		A[r]=temp;
	return i;
}
//int k=10;
//MinK(a, 0, a.length-1, k-1);

下面是几篇详细分析各种方式的文章。也包括了一些时间复杂度的证明。认真看看的确有比较大收获

寻找最小的k个数(@v_JULY_v)  

编程之美_2.5_寻找最大的k个数(@insistGoGo)

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值