查询中位数
给定线性序集中n个元素和一个整数k 【k=(n+1)/2】,要求找出这n个元素中第k小的元素,即找中位数。线性序列没有排序,没有重复值。
已知快速排序划分时一个划分基准数的位置在确定后,在之后排序中是不会变的。利用此特性,以下算法模仿快速排序,但只对划分出的子数组之一进行处理,时间复杂度为O(n),比排序完查找更快。
如果n=0或1,不需要找(唯一一个数据就是要找的)
如果当前基准数不是所求的,在相应的子串中找(相当于快排中划分好了的三段)
查询中位数将k设为n/2。以下可以查询第k小的任何一个数。
由于使用分治策略,这里的k是在子串中相对的第k小(k是相对left和right的位置),但是划分函数返回的下标mid是绝对位置,所以,可以用一个变量(j)存储mid在子串中的相对位置。
SelectK每一次递归时left和right也会发生变化,相应的k也要注意改变(k-j)。
int SelectK(int* nums, int left, int right, int k)
{
if (left == right&&k==1)//剩下一个元素找里面第一小的元素
return nums[left];
int mid = Partition(nums, left, right);
int j = mid + 1 - left;//j是相对在子串中的位置(第j个)
if (k <= j)return SelectK(nums, left, mid, k);
else return SelectK(nums, mid + 1, right, k-j);
}
int SelectKMin(int* nums, int n, int k)
{
if (nullptr == nums || n < 1 || k<1 || k>n)
return -1;
return SelectK(nums, 0, n - 1, k);
}
划分函数
同快排的划分
int Partition(int*nums,int left,int right)
{
int tmp = nums[left];
while (left < right)
{
while (left<right && nums[right]>tmp)right--;
if(left<right)nums[left] = nums[right];
while (left < right && nums[left] < tmp)left++;
if (left < right)nums[right] = nums[left];
}
nums[left] = tmp;
return left;
}
测试
int main()
{
int a[] = { 5,3,6,7,9,8,4,2,1,0 };
int n = sizeof(a) / sizeof(a[0]);
int k = 0;
for (k = 1; k <= n; k++)
{
int kmin = SelectKMin(a, n, k);
printf("k:%d kmin:%d\n", k, kmin);
}
}
一维最接近点对
给定一维线上n个点,找其中的一对点,使得在n个点组成的所有点对中,该点对的距离最小。如果有多于一对只找1对作为解。
如果暴力求解每一对之间的距离需要O(n^2)太慢了。将每个点的位置存在数组中,利用分治法:
将数组划分后产生两个子集合,差值最小有三个部位可能产生:
当集合中元素个数<2时,无法计算差值,此时应返回INT_MAX
设左边集合为S1,最小差为d1;右边集合为S2,最小差d2;S2中最小值q和S1最大值p相减得的差,三个数相比最小的值即为最接近点对的差值。
为了将数组平分减少计算次数,需要找中位数并用其作为划分基准。
主要代码:
int CpairNum(int* nums, int left, int right)
{
if ((right - left) < 1)return INT_MAX;//少于两个数没有差值
int m = (right - left + 1) / 2;//当前数组中位数的位置(left~right)
int pos = left + m - 1;//绝对位置(0~n-1)
SelectK(nums, left, right, m);//找中位数的值,然后划分成左右两个数组
int d1 = CpairNum(nums, left,pos);//S1继续递归
int d2 = CpairNum(nums,pos+1 ,right );//S2继续递归
int p = MaxS1(nums, left ,pos);//S1中最大的元素
int q = MinS2(nums,pos+1, right);//S2中最小的元素
return Min3(d1, d2, q - p);//对比找到最小元素
}
int Cpair(int* nums, int n)
{
if (n < 2 || nums == nullptr)return INT_MAX;
return CpairNum(nums, 0, n - 1);
}
其余代码:
//划分函数
int Partition(int* nums, int left, int right)
{
int tmp = nums[left];
while (left < right)
{
while (left<right && nums[right]>tmp)right--;
if (left < right)nums[left] = nums[right];
while (left < right && nums[left] < tmp)left++;
if (left < right)nums[right] = nums[left];
}
nums[left] = tmp;
return left;
}
//找第k小元素下标
int SelectK(int* nums, int left, int right, int k)
{
if (left == right && k == 1)
return nums[left];
int mid = Partition(nums, left, right);
int j = mid - left + 1;
if (k == j)return nums[mid];
if (k <j)return SelectK(nums, left, mid, k);
else return SelectK(nums, mid + 1, right, k - j);
}
//S1里面最大的
int MaxS1(int*nums,int left ,int right)
{
return nums[left];
}
//S2里面最大的
int MinS2(int *nums,int left,int right)
{
int min = INT_MAX;
for (int i = left; i <= right; i++)
{
if (nums[i] < min)
{
min = nums[i];
}
}
return min;
}
//三个最小差里面最小的
int Min(int a, int b)
{
return a < b ? a : b;
}
int Min3(int a, int b, int c)
{
return Min(a, Min(b, c));
}
排好序再计算的方法无法直接推广到二维的情形,而分治法求解可以。