一.什么是中位数?
在一个n个元素顺序排列的集合中,一个中位数是它所属集合的中点元素。
用公式表达中位数的位置就是:
1.当n为奇数时:
i=(n+1)/2;
2.当n为偶数时,有两个中位数:
i=n/2 ;
i=n/2+1;
因此,若不考虑n的奇偶性,中位数总是出现在 :i=(n+1)/2处和i=(n+2)/2处。
为了方便起见,以后博客的内容默认中位数在:(n+1)/2处
二.本章主要内容:
本章将讨论从一个有由n个互不相同的元素组成的集合中选择第i个顺序统计量(就是在顺序的排序的下第i个元素)的问题。
于是我们可以对输入和输出有如下的定义:
输入:一个包含n个数的集合A和一个整数i,1<=i<=n。
输出:元素x,且在A中恰好有i-1个小于它的元素。
我们很容易的知道可以通过排序算法将数组A排个序,然后找出第i个元素就可以解决这个问题。但是有没有更快的算法呢?
当然有,这个就是本章的内容。
三.具体策略:
1.特例:最小值和最大值
我们要知道:如果我们为了确定n个元素中的最小值或最大值,必须要做n-1次比较。
那么如果我们要同时确定最大值和最小值,就一定要进行2n-2次比较吗?
事实上,我们有只需要3n/2次比较就可以同时找到最大值和最小值的方法!!
做法如下:
(1)首先,我们将一对输入元素相互进行比较
(2)然后把较小的与当前最小值比较,把最大的与当前最大值进行比较。
这样,对每两个元素共需三次比较。
那么如何设定当前的最小值和最大值呢?
这个取决于n是奇数还是偶数:如果n是奇数,最大值最小值都是第一个元素。
如果n是偶数,就对前两个元素做一次比较来决定最大值最小值。
算法java代码实现:
public class MinAMax {
int max;
int min;
public void MaxMin(int a[]){
if(a.length%2==1){//如果数组a中元素有奇数个
max=min=a[0];//将第一个元素默认为最大值和最小值
for(int i=1;i<a.length;i+=2) {//从第二个元素开始成对向后循环,直到结束
if(a[i]>a[i+1])//比较两个元素,如果前者大于后者,互换
Swap(a,i,i+1);
if(a[i]<min)//将较小者与当前最小值比较
min=a[i];
if(a[i+1]>max)//将较大者与当前最大值比较
max=a[i+1];
}
} else{//否则数组a中元素有偶数数个
//将前两个元素的值赋给最大和最小值
if(a[0]>a[1]) {
max=a[0];
min=a[1];
} else{
max=a[1];
min=a[0];
}
for(int i=2;i<a.length;i+=2){//从第三个元素开始成对向后循环,直到结束
if(a[i]>a[i+1])
Swap(a,i,i+1);
if(a[i]<min)
min=a[i];
if(a[i+1]>max)
max=a[i+1];
}
}
}
//交换函数,事项数组中两个i,j位置元素互换
void Swap(int a[],int j,int i){
int m;
m=a[i];
a[i]=a[j];
a[j]=m;
}
public static void main(String[] args) {
int a[]={3,2,65,6,8,6};
MinAMax text=new MinAMax();
text.MaxMin(a);
System.out.println("最大值为:"+text.max);
System.out.println("最小值为:"+text.min);
}
}
结果:
2.一般情况:
主要是分治思想
用到了快排的随机key值的分区方法:
这个方法的时间复杂度仅为O(n);
方法代码:
/*
查找第i大元素方法
参数:a:输入数组 p:开始位置 r:结束位置
基本思想:
用到分治思想
*/
/**
* 此方法返回数组中第i小的元素
* @param a 输入数组
* @param start 开始索引
* @param end 结束索引
* @param i 规定输出第i小元素
* @return
*/
public int RandonMized_select(int[] a, int start, int end, int i) {
if (start == end)
return a[start];
int keyIndex=Randomized_partition(a, start, end);
// for (int n:a){
// System.out.print(n+" ");
// }
int k=keyIndex-start+1;
//System.out.println("["+a[k]+"]");
if(i==k){
return a[keyIndex];
}else if(i<k){
return RandonMized_select(a, start,keyIndex-1,i);
}else {
return RandonMized_select(a, keyIndex + 1, end, i - k);
}
}
//快排一次过程
int partition(int[] a, int start, int end) {
int key=a[end];
int i=start-1;
for(int j = start; j<end;j++) {
if(a[j]<=key) {
i=i+1;
Swap(a,i,j);
}
}
Swap(a,i+1, end);
return i+1;
}
//随机产生key值进行一次快排分区
int Randomized_partition(int[] a, int start, int end)
{
int i = (int)(Math.random() * (end));
if(i<start){
i=start;
}
Swap(a,end,i);
return partition(a,start,end);
}
测试代码:
public class MinAMax {
int max;
int min;
public static void main(String[] args) {
int a[]={3,2,65,6,8,9,7};
MinAMax text=new MinAMax();
int target=text.RandonMized_select(a,0,a.length-1,4);//在一个数组的0到a.length位置区域中找第4大的元素
System.out.println(target);
结果如下: