中值算法

引言

一个数据集合的中值(Median)通常是很一个很有价值的统计指标,由于它对异常数据不敏感,所以一般会比平均值(Mean)更能体现数据集合数据的“平均水平”。然而,对于无序数据序列求中值在实现上却没有求平均值那样简单优美的O (N)复杂度的算法。最容易想到的做法是先对数据进行排序,然后取中点的值,然而这种做法的时间复杂度是O(NlogN)。有没有更快的算法呢?

本文介绍两种更快的算法:第一种是利用快速排序原理的准确的随机选择算法;第二种是一种近似算法,所获得的值虽然可能不是很精确,但一般会比第一种方法更快。 

本报告的算法实现伪代码除注释部分外均引自参考文献,若只需理解实现思想,不需要钻研伪代码的细节,可先直接看正文与注释部分。本报告提供了这两种算法相对先排序后取中值算法的时间性能比较的实验结果。

随机选择算法

这一算法是一个通用的选择算法,它可以求一个可排序数据序列中第k个位置的值。其算法伪代码如下:

// 分割,即一般快排的分割方法,返回分割点

PARTITION(A, p, r)

  x ß A[r]

  i ß p-1

  for j ß p to r-1

     do if A[j] <= x

        then i ß i+1

            exchange A[i] <-> A[j]

   exchange A[i+1] <-> A[r]

   return i+1

 

// 随机分割,在选择分割点时引入随机性,使快排的最坏情况难以发生

RANDOMIZED-PARTITION(A, p, r)

   i ß RANDOM(p, r)

   exchange A[r] <-> A[i]

   return PARTITION(A, p, r)

 

// 随机选择

RANDOMIZED-SELECT(A, p, r, i)

   if p = r

     then return A[p]

   q ß RANDOMIZED-PARTITION(A, p, r)

   k ß q – p +1

   if  i = k

      then return A[q]

   elseif i < k

      then return RANDOMIZED-PARTITION(A, p, q-1, i)

   else return RANDOMIZED-PARTITION(A, q+1, r, i-k)

其基本思想是利用快速排序的原理,对数据进行分割,将分割点的位置与k做比较,若相等,返回k位置的值,否则在分割点左右两个集合的其中一个中继续查找(快速排序需要对前后两个集合都进一步进行排序,而选择算法只需要对其中一个集合再处理,这是它能比排序算法更快的关键)。本算法的“随机”是在选取分割点时进行了随机化,这样可以避免出现最坏状况。本算法的算法复杂度为O(N)。

近似中值选择算法

本算法是一个近似算法,先看一个数组元素个数是3r的算法版本,其算法伪代码如下:

// 三重调整,对三个数进行调整,将中位数移到中间位置

TRIPLET_ADJUST(A, i, Step)

   if (A[i] < A[j])

     then

         if (A[k] < A[i] then swap (A[i], A[j]);

         else if (A[k] < A[j] then swap(A[j], A[k])

     else

         if (A[i] < A[k]) then swap(A[i], A[j]);

         else if (A[k] > A[j]) then swap(A[j], A[k]);

 

// 中值近似,求一个数组A[0, 3r-1]的中值

APPROXIMATE_MEDIAN(A, r)

   Step=1; Size=3^r;

   Repeat r times

      i = (Step-1)/2;

      while ( i < Size) do

           TRIPLET_ADJUST(A, I, Step);

           i = i +(3*Step)

      end while

      Step = 3*Step

end repeat

return A[(Size-1)/2]

这一算法的基本思想是连续进行三重调整,即对每三个数取其中值,将取出的小区域中值组成一个新的数组再取其中值,直到最后。

   以上算法只对n=3r时适用,对于一般情况如何扩展使用呢?以下是其基本思想:

(1)       对于任意的n, n = 3*t + k  = 3*(t-1) + (3 + k)  k∈{0, 1, 2} 对于前面的(t-1)个三重组使用以上三重调整算法,获取其中值序列,对于剩下的(3+k)个数,使用选择排序算法,获取其中值,与前面中值序列合并作为下一轮中值候选数组。

(2)       为了防止某些数每一轮被剩下,在选择三重组时从左到右、从右到左轮流进行。

 其具体算法伪代码如下:

// 选择排序,就是一般的选择排序算法,没什么特别

SELECTION_SORT(A, Left, Size, Step)

  for (i=Left; i < Left+(Size-1)*Step; i=i+Step)

     min = i;

     for (j=i+Step; j<Left+Size*Step; j=j+Step)

         if (A[j].Key < A[min].Key) then min = j;

     end for;

     swap(A[i], A[min]);

  end for

 

// 任何大小数组中值近似,利用三重调整与选择排序算法实现,获取A[0, Size-1]的中值

APPROXIMATE_MEDIAN_ANYN(A, Size)

   LeftToRight = False;  Left=0;  Step=1;

   while (Size > Threshod) do

      LeftToRight = Not(LeftToRight);

      Rem = (Size mod 3);

      // 根据是从左至右还是从右至左设置调整初始位置

      if (LeftToRight) then i = Left;

                    else i = Left + (3 + Rem)*Step;

      // 对于t-1个三重组进行三重调整

      repeat (Size/3-1) times

         TRIPLET_ADJUST (A, i, Step);

         i = i+3*Step;

      end repeat;

      if (LeftToRight) then Left = Left+Step;

                    else i=Left;

                        Left=Left+(1+Rem)*Step;

      // 对剩下的3+Rem值进行选择排序

SELECTION_SORT(A, i, 3+Rem, Step);

if (Rem=2) then

          if (LeftToRight) then swap(A[i+Step], A[i+2*Step])

                        else swap(A[i+2*Step], A[i+3Step]);

Step=3*Step; Size=Size/3;

end while

SELECTION_SORT(A, Left, Size, Step);

return A[Left+Step*Floor((Size-1)/2)];

    可以证明,对于n=3r的情形,以上算法平均进行少于4n/3次比较和n/3次交换,最坏情形是进行小于3n/2次比较和n/2次交换。

实验结果

为了比较以上算法与先进行排序再取中值的实际性能差别,我们将以上算法用C++实现并与使用STL的sort排序后再取中值进行比较,N分别取值106, 107, 108,数组值初始化为0-N的随机值。结果如下:

不同算法运行时间比较(单位s):

N值

求平均值

std::sort后求中值

随机选择算法

近似中值选择算法

1,000,000

0.01

0.23

0.07

0.03

10,000,000

0.09

2.79

0.6

0.26

100,000,000

0.92

32.79

4.68

2.51

 从以上实验结果可知,近似中值选择算法一般比先排序的算法快10倍左右,随机选择算法比先排序的算法快5倍左右。近似中值选择算法所用时间为求平均值算法的时间的3倍左右。

小结

对于大数据集合,本文所介绍的随机选择算法和近似中值选择算法比快速排序分别快5倍与10倍左右;而且随机选择算法是一个通用的选择算法,可以用来方便地求k位置值。在恰当时侯值得一用。

转载于:https://www.cnblogs.com/zmy235/p/4868930.html

<think>首先,用户的问题是关于在C++中实现中值算法中值算法通常指的是找到一组数的中位数,即排序后中间的值。从系统级指令中,我需要: -使用$...$格式行内数学表达式。-独立公式使用$$...$$格式。-确保LaTeX语法正确。-使用中文回答。-生成相关问题。 -在回答中引用的段落末尾添加引用标识。-结构清晰,帮助用户逐步解决问题。-保证回答真实可靠。 参考站内引用:-引用[1]:展示了C++实现MEDIAN定数值的中值算法,使用分块和递归方法。-引用[2]:实现中值滤波,使用冒泡排序。 -引用[3]:讨论中值滤波原理和排序方法。 -引用[4]:自适应中值滤波。 -引用[5]:提到master公式和时间复杂度。用户的问题聚焦于实现中值算法,所以我应该基于这些引用提供可靠的实现。关键步骤: 1. **理解中值算法**:中位数是排序后中间的值。对于奇数个元素,是中间值;对于偶数个元素,通常是中间两个值的平均。 2. **实现方法**:-简单方法:排序数组,然后取中间值。-高效方法:如引用[1]所示,使用分块和递归,类似快速选择算法,平均时间复杂度为O(n)。 3. **代码示例**:提供C++代码,确保正确。 在回答中:-使用中文。-结构清晰:先解释概念,然后逐步实现。-添加引用:在相关部分末尾添加引用标识。-生成相关问题。 实现步骤:1.**排序后取中值**:简单但O(n logn)时间。 2. **快速选择**:更高效,O(n)平均时间。 3. **处理边界条件**:如空数组或小数组。从引用[1],代码使用分块方法,但它似乎有错误:它递归地修改数组,可能导致问题。标准方法是快速选择。我应该提供更可靠的实现。参考快速选择算法: -选择pivot。-分区数组。 -递归地在包含中值的部分搜索。数学表达式: -对于中值,公式:如果n是奇数,中值是$x_{(k)}$,其中$k =\frac{n+1}{2}$;如果n是偶数,中值是$\frac{x_{(k)}+ x_{(k+1)}}{2}$,其中$k= \frac{n}{2}$。-在行内使用$...$。回答结构:1.**介绍中值算法**:解释什么是中位数。2.**实现方法**:-简单方法:排序后取中值。-高效方法:快速选择。3.**代码示例**:提供C++代码。4.**添加引用**:基于站内引用。 5. **生成相关问题**:在末尾。 生成相关问题:-如何优化中值算法?-中值算法的时间复杂度是多少?-中值滤波的应用? 确保LaTeX:-行内:例如,时间复杂度$O(n)$。 -独立:如果有公式,但可能不需要复杂公式。最终代码: -实现一个函数findMedian,处理数组。-包括错误处理(如空数组)。 -使用标准库如<algorithm>for sortornth_element.使用nth_element forefficient implementation.</think>### 在C++中实现中值算法 中值算法(Median Algorithm)用于计算一组数据的中位数。中位数是数据排序后位于中间的值:如果数据个数$n$为奇数,中位数是第$\frac{n+1}{2}$个元素;如果$n$为偶数,中位数是中间两个元素的平均值,即$\frac{x_{(k)} + x_{(k+1)}}{2}$,其中$k = \frac{n}{2}$[^3]。在C++中,实现中值算法有多种方法,包括简单排序法和高效选择法。我将逐步解释并提供一个可靠且高效的实现。 #### 步骤1: 理解核心概念 - **中位数定义**:对于数据集$S = \{x_1, x_2, \dots, x_n\}$,排序后为$x_{(1)} \leq x_{(2)} \leq \dots \leq x_{(n)}$,中位数$M$定义为: $$ M = \begin{cases} x_{\left(\frac{n+1}{2}\right)} & \text{if } n \text{ is odd} \\ \frac{x_{\left(\frac{n}{2}\right)} + x_{\left(\frac{n}{2} + 1\right)}}{2} & \text{if } n \text{ is even} \end{cases} $$ - **方法选择**: - **简单方法**:先排序数组,然后直接取中间值。时间复杂度为$O(n \log n)$,适用于小数据集[^3]。 - **高效方法**:使用快速选择算法(QuickSelect),平均时间复杂度为$O(n)$,无需完全排序数组[^1][^5]。快速选择基于快速排序的分区思想,但只递归搜索包含中值的部分。 #### 步骤2: 实现高效中值算法快速选择) 快速选择算法是标准库`std::nth_element`的基础,它能在$O(n)$平均时间内找到第k小的元素。以下是完整的C++实现,包括错误处理和边界条件: - **优点**:高效、代码简洁,适合大数组。 - **关键点**: - 使用`std::nth_element`进行部分排序。 - 处理空数组和单元素数组的特殊情况。 - 计算中值时区分奇偶长度。 ```cpp #include <iostream> #include <vector> #include <algorithm> // 用于std::nth_element和std::sort // 函数:计算向量的中值 double findMedian(std::vector<int>& nums) { if (nums.empty()) { // 空数组返回NaN或抛出异常,这里使用NaN表示错误 return std::numeric_limits<double>::quiet_NaN(); } int n = nums.size(); int midIndex = n / 2; // 中间位置索引 // 如果数组小,直接排序取中值(避免快速选择开销) if (n <= 10) { std::sort(nums.begin(), nums.end()); if (n % 2 == 1) { return static_cast<double>(nums[midIndex]); // 奇数个元素 } else { // 偶数个元素,取中间两个值的平均 return (static_cast<double>(nums[midIndex - 1]) + static_cast<double>(nums[midIndex])) / 2.0; } } // 高效方法:快速选择找到中间元素 std::nth_element(nums.begin(), nums.begin() + midIndex, nums.end()); if (n % 2 == 1) { return static_cast<double>(nums[midIndex]); // 奇数个元素 } else { // 偶数个元素:需要找到第二个中间值 // 先获取第一个中间值(此时nums[midIndex]已部分排序) int median1 = nums[midIndex]; // 在剩余部分找到第二个中间值(索引midIndex-1) std::nth_element(nums.begin(), nums.begin() + midIndex - 1, nums.end()); int median2 = nums[midIndex - 1]; return (static_cast<double>(median1) + static_cast<double>(median2)) / 2.0; } } int main() { // 示例用法 std::vector<int> data = {9, 1, 6, 3, 7, 2, 8, 4, 5}; // 奇数个元素 double median = findMedian(data); std::cout << "中值: " << median << std::endl; // 输出应为 5 std::vector<int> dataEven = {1, 2, 3, 4}; // 偶数个元素 median = findMedian(dataEven); std::cout << "中值: " << median << std::endl; // 输出应为 2.5 return 0; } ``` #### 代码说明 - **错误处理**:空数组返回`NaN`,避免崩溃[^1]。 - **优化**:对小数组($n \leq 10$)使用直接排序,因为快速选择在小数组上开销较大;对大数组使用`std::nth_element`实现快速选择,平均时间复杂度为$O(n)$[^5]。 - **奇偶处理**:通过`midIndex`计算中值位置,并正确区分奇偶情况。 - **性能**:平均时间复杂度$O(n)$,最坏情况$O(n^2)$(但概率低),空间复杂度$O(1)$(原地操作)[^1][^5]。 #### 注意事项 - **引用依据**:此实现参考了MEDIAN算法快速选择思想,确保了高效性和可靠性[^1][^5]。对于中值滤波等应用,排序步骤可优化为冒泡或插入排序以节省内存[^2][^3]。 - **测试建议**:使用不同数据集测试(如奇数/偶数长度、重复元素),确保鲁棒性。对数器(如引用[5]所述)可用于验证正确性。 #### 应用场景 中值算法常用于: - 数据滤波(如中值滤波去除图像噪声)[^2][^4]。 - 统计分析(如金融数据的中值计算)。 - 算法优化(如分治策略中的基准值选择)。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值