当前位置:   article > 正文

【分治策略】查询中位数&最接近点对_给定线性序集中的n个元素,需求在线性时间内找出这n个元素的中位数,请给出算法

给定线性序集中的n个元素,需求在线性时间内找出这n个元素的中位数,请给出算法

查询中位数

给定线性序集中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)。

  1. int SelectK(int* nums, int left, int right, int k)
  2. {
  3. if (left == right&&k==1)//剩下一个元素找里面第一小的元素
  4. return nums[left];
  5. int mid = Partition(nums, left, right);
  6. int j = mid + 1 - left;//j是相对在子串中的位置(第j个)
  7. if (k <= j)return SelectK(nums, left, mid, k);
  8. else return SelectK(nums, mid + 1, right, k-j);
  9. }
  10. int SelectKMin(int* nums, int n, int k)
  11. {
  12. if (nullptr == nums || n < 1 || k<1 || k>n)
  13. return -1;
  14. return SelectK(nums, 0, n - 1, k);
  15. }

划分函数

同快排的划分

  1. int Partition(int*nums,int left,int right)
  2. {
  3. int tmp = nums[left];
  4. while (left < right)
  5. {
  6. while (left<right && nums[right]>tmp)right--;
  7. if(left<right)nums[left] = nums[right];
  8. while (left < right && nums[left] < tmp)left++;
  9. if (left < right)nums[right] = nums[left];
  10. }
  11. nums[left] = tmp;
  12. return left;
  13. }

测试

  1. int main()
  2. {
  3. int a[] = { 5,3,6,7,9,8,4,2,1,0 };
  4. int n = sizeof(a) / sizeof(a[0]);
  5. int k = 0;
  6. for (k = 1; k <= n; k++)
  7. {
  8. int kmin = SelectKMin(a, n, k);
  9. printf("k:%d kmin:%d\n", k, kmin);
  10. }
  11. }

一维最接近点对

给定一维线上n个点,找其中的一对点,使得在n个点组成的所有点对中,该点对的距离最小。如果有多于一对只找1对作为解。

如果暴力求解每一对之间的距离需要O(n^2)太慢了。将每个点的位置存在数组中,利用分治法:

将数组划分后产生两个子集合,差值最小有三个部位可能产生:

当集合中元素个数<2时,无法计算差值,此时应返回INT_MAX

设左边集合为S1,最小差为d1;右边集合为S2,最小差d2;S2中最小值q和S1最大值p相减得的差,三个数相比最小的值即为最接近点对的差值。

为了将数组平分减少计算次数,需要找中位数并用其作为划分基准。

主要代码:

  1. int CpairNum(int* nums, int left, int right)
  2. {
  3. if ((right - left) < 1)return INT_MAX;//少于两个数没有差值
  4. int m = (right - left + 1) / 2;//当前数组中位数的位置(left~right)
  5. int pos = left + m - 1;//绝对位置(0~n-1)
  6. SelectK(nums, left, right, m);//找中位数的值,然后划分成左右两个数组
  7. int d1 = CpairNum(nums, left,pos);//S1继续递归
  8. int d2 = CpairNum(nums,pos+1 ,right );//S2继续递归
  9. int p = MaxS1(nums, left ,pos);//S1中最大的元素
  10. int q = MinS2(nums,pos+1, right);//S2中最小的元素
  11. return Min3(d1, d2, q - p);//对比找到最小元素
  12. }
  13. int Cpair(int* nums, int n)
  14. {
  15. if (n < 2 || nums == nullptr)return INT_MAX;
  16. return CpairNum(nums, 0, n - 1);
  17. }

其余代码:

  1. //划分函数
  2. int Partition(int* nums, int left, int right)
  3. {
  4. int tmp = nums[left];
  5. while (left < right)
  6. {
  7. while (left<right && nums[right]>tmp)right--;
  8. if (left < right)nums[left] = nums[right];
  9. while (left < right && nums[left] < tmp)left++;
  10. if (left < right)nums[right] = nums[left];
  11. }
  12. nums[left] = tmp;
  13. return left;
  14. }
  15. //找第k小元素下标
  16. int SelectK(int* nums, int left, int right, int k)
  17. {
  18. if (left == right && k == 1)
  19. return nums[left];
  20. int mid = Partition(nums, left, right);
  21. int j = mid - left + 1;
  22. if (k == j)return nums[mid];
  23. if (k <j)return SelectK(nums, left, mid, k);
  24. else return SelectK(nums, mid + 1, right, k - j);
  25. }
  26. //S1里面最大的
  27. int MaxS1(int*nums,int left ,int right)
  28. {
  29. return nums[left];
  30. }
  31. //S2里面最大的
  32. int MinS2(int *nums,int left,int right)
  33. {
  34. int min = INT_MAX;
  35. for (int i = left; i <= right; i++)
  36. {
  37. if (nums[i] < min)
  38. {
  39. min = nums[i];
  40. }
  41. }
  42. return min;
  43. }
  44. //三个最小差里面最小的
  45. int Min(int a, int b)
  46. {
  47. return a < b ? a : b;
  48. }
  49. int Min3(int a, int b, int c)
  50. {
  51. return Min(a, Min(b, c));
  52. }

排好序再计算的方法无法直接推广到二维的情形,而分治法求解可以。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/article/detail/55242
推荐阅读
相关标签
  

闽ICP备14008679号