计算c#的中位数

时间:2010-11-10 02:31:58

标签: c# .net algorithm median

我需要编写接受小数组数组的函数,它会找到中位数。

.net Math库中是否有函数?

13 个答案:

答案 0 :(得分:56)

看起来其他答案正在使用排序。从性能的角度来看,这不是最佳的,因为它需要O(n logn)时间。可以在O(n)时间内计算中位数。此问题的通用版本称为" n阶统计数据"这意味着在一个集合中找到一个元素K,使得我们有n个元素小于或等于K,其余大于或等于K.所以0阶统计量将是集合中的最小元素(注意:有些文献使用从1到N的索引)而不是0到N-1)。中位数只是(Count-1)/2 - 订单统计。

以下是Cormen等人,第3版 算法导论中采用的代码。

/// <summary>
/// Partitions the given list around a pivot element such that all elements on left of pivot are <= pivot
/// and the ones at thr right are > pivot. This method can be used for sorting, N-order statistics such as
/// as median finding algorithms.
/// Pivot is selected ranodmly if random number generator is supplied else its selected as last element in the list.
/// Reference: Introduction to Algorithms 3rd Edition, Corman et al, pp 171
/// </summary>
private static int Partition<T>(this IList<T> list, int start, int end, Random rnd = null) where T : IComparable<T>
{
    if (rnd != null)
        list.Swap(end, rnd.Next(start, end+1));

    var pivot = list[end];
    var lastLow = start - 1;
    for (var i = start; i < end; i++)
    {
        if (list[i].CompareTo(pivot) <= 0)
            list.Swap(i, ++lastLow);
    }
    list.Swap(end, ++lastLow);
    return lastLow;
}

/// <summary>
/// Returns Nth smallest element from the list. Here n starts from 0 so that n=0 returns minimum, n=1 returns 2nd smallest element etc.
/// Note: specified list would be mutated in the process.
/// Reference: Introduction to Algorithms 3rd Edition, Corman et al, pp 216
/// </summary>
public static T NthOrderStatistic<T>(this IList<T> list, int n, Random rnd = null) where T : IComparable<T>
{
    return NthOrderStatistic(list, n, 0, list.Count - 1, rnd);
}
private static T NthOrderStatistic<T>(this IList<T> list, int n, int start, int end, Random rnd) where T : IComparable<T>
{
    while (true)
    {
        var pivotIndex = list.Partition(start, end, rnd);
        if (pivotIndex == n)
            return list[pivotIndex];

        if (n < pivotIndex)
            end = pivotIndex - 1;
        else
            start = pivotIndex + 1;
    }
}

public static void Swap<T>(this IList<T> list, int i, int j)
{
    if (i==j)   //This check is not required but Partition function may make many calls so its for perf reason
        return;
    var temp = list[i];
    list[i] = list[j];
    list[j] = temp;
}

/// <summary>
/// Note: specified list would be mutated in the process.
/// </summary>
public static T Median<T>(this IList<T> list) where T : IComparable<T>
{
    return list.NthOrderStatistic((list.Count - 1)/2);
}

public static double Median<T>(this IEnumerable<T> sequence, Func<T, double> getValue)
{
    var list = sequence.Select(getValue).ToList();
    var mid = (list.Count - 1) / 2;
    return list.NthOrderStatistic(mid);
}

很少注意到:

  1. 此代码将尾部递归代码从书中的原始版本替换为迭代循环。
  2. 当start == end时,它还消除了原始版本中不必要的额外检查。
  3. 我提供了两个版本的Median,一个接受IEnumerable,然后创建一个列表。如果您使用接受IList的版本,请记住它会修改列表中的顺序。
  4. 以上方法计算O(n) 预期时间中的中位数或任何i顺序统计数据。如果您希望O(n) 更糟糕的时间,那么有技巧可以使用中位数中位数。虽然这会改善较差的案例性能,但它会降低平均情况,因为O(n)中的常量现在更大。但是,如果您计算中位数主要是在非常大的数据上,那么值得一看。
  5. NthOrderStatistics方法允许传递随机数生成器,然后在分区期间用于选择随机数。这通常是没有必要的,除非您知道您的数据具有某些模式,以便最后一个元素不会足够随机,或者您的代码以某种方式暴露在外面以进行有针对性的利用。
  6. 如果你有奇数个元素,那么中位数的定义是明确的。它只是排序数组中索引为(Count-1)/2的元素。但是当偶数元素(Count-1)/2不再是一个整数并且你有两个中位数时:降低中位数Math.Floor((Count-1)/2)Math.Ceiling((Count-1)/2)。有些教科书使用较低的中位数作为&#34;标准&#34;而其他人则建议平均使用两个。对于2个元素的集合,这个问题变得特别重要。以上代码返回较低的中位数如果您想要平均较低和较高,则需要在上面的代码上调用两次。在这种情况下,请确保测量数据的性能,以确定是否应该使用上面的代码VS直接排序。
  7. 对于.net 4.5+,您可以在MethodImplOptions.AggressiveInlining方法上添加Swap<T>属性,以略微提升效果。

答案 1 :(得分:34)

感谢Rafe,这会考虑到您的回复者发布的问题。

public static double GetMedian(double[] sourceNumbers) {
        //Framework 2.0 version of this method. there is an easier way in F4        
        if (sourceNumbers == null || sourceNumbers.Length == 0)
            throw new System.Exception("Median of empty array not defined.");

        //make sure the list is sorted, but use a new array
        double[] sortedPNumbers = (double[])sourceNumbers.Clone();
        Array.Sort(sortedPNumbers);

        //get the median
        int size = sortedPNumbers.Length;
        int mid = size / 2;
        double median = (size % 2 != 0) ? (double)sortedPNumbers[mid] : ((double)sortedPNumbers[mid] + (double)sortedPNumbers[mid - 1]) / 2;
        return median;
    }

答案 2 :(得分:18)

  

.net Math库中是否有函数?

没有

虽然写自己并不难。朴素算法对数组进行排序,并选择中间(或两个中间的)平均元素。但是,此算法为O(n log n),但可以在O(n)时间内解决此问题。您想查看selection algorithms以获得此类算法。

答案 3 :(得分:17)

decimal Median(decimal[] xs) {
  Array.Sort(xs);
  return xs[xs.Length / 2];
}

应该做的伎俩。

- 编辑 -

对于那些想要完整monty的人来说,这里是完整的,简短的纯解决方案(假设是非空的输入数组):

decimal Median(decimal[] xs) {
  var ys = xs.OrderBy(x => x).ToList();
  double mid = (ys.Count - 1) / 2.0;
  return (ys[(int)(mid)] + ys[(int)(mid + 0.5)]) / 2;
}

答案 4 :(得分:10)

Math.NET是一个开源库,提供了计算Median的方法。 nuget包名为MathNet.Numerics

用法非常简单:

using MathNet.Numerics.Statistics;

IEnumerable<double> data;
double median = data.Median();

答案 5 :(得分:3)

这是Jason的答案的通用版本

    /// <summary>
    /// Gets the median value from an array
    /// </summary>
    /// <typeparam name="T">The array type</typeparam>
    /// <param name="sourceArray">The source array</param>
    /// <param name="cloneArray">If it doesn't matter if the source array is sorted, you can pass false to improve performance</param>
    /// <returns></returns>
    public static T GetMedian<T>(T[] sourceArray, bool cloneArray = true) where T : IComparable<T>
    {
        //Framework 2.0 version of this method. there is an easier way in F4        
        if (sourceArray == null || sourceArray.Length == 0)
            throw new ArgumentException("Median of empty array not defined.");

        //make sure the list is sorted, but use a new array
        T[] sortedArray = cloneArray ? (T[])sourceArray.Clone() : sortedArray = sourceArray;
        Array.Sort(sortedArray);

        //get the median
        int size = sortedArray.Length;
        int mid = size / 2;
        if (size % 2 != 0)
            return sortedArray[mid];

        dynamic value1 = sortedArray[mid];
        dynamic value2 = sortedArray[mid - 1];
        return (sortedArray[mid] + value2) * 0.5;
    }

答案 6 :(得分:1)

这是最快的不安全实施, 之前的相同算法,取自此source

    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    private static unsafe void SwapElements(int* p, int* q)
    {
        int temp = *p;
        *p = *q;
        *q = temp;
    }

    public static unsafe int Median(int[] arr, int n)
    {
        int middle, ll, hh;

        int low = 0; int high = n - 1; int median = (low + high) / 2;
        fixed (int* arrptr = arr)
        {
            for (;;)
            {
                if (high <= low)
                    return arr[median];

                if (high == low + 1)
                {
                    if (arr[low] > arr[high])
                        SwapElements(arrptr + low, arrptr + high);
                    return arr[median];
                }

                middle = (low + high) / 2;
                if (arr[middle] > arr[high])
                    SwapElements(arrptr + middle, arrptr + high);

                if (arr[low] > arr[high])
                    SwapElements(arrptr + low, arrptr + high);

                if (arr[middle] > arr[low])
                    SwapElements(arrptr + middle, arrptr + low);

                SwapElements(arrptr + middle, arrptr + low + 1);

                ll = low + 1;
                hh = high;
                for (;;)
                {
                    do ll++; while (arr[low] > arr[ll]);
                    do hh--; while (arr[hh] > arr[low]);

                    if (hh < ll)
                        break;

                    SwapElements(arrptr + ll, arrptr + hh);
                }

                SwapElements(arrptr + low, arrptr + hh);

                if (hh <= median)
                    low = ll;
                if (hh >= median)
                    high = hh - 1;
            }
        }
    }

答案 7 :(得分:1)

CenterSpace的NMath库提供了一个功能:

double[] values = new double[arraySize];
double median = NMathFunctions.Median(values);

您可以选择使用NaNMedian(如果您的数组可能包含空值),但您需要将数组转换为向量:

double median = NMathFunctions.NaNMedian(new DoubleVector(values));

CenterSpace's NMath Library不是免费的,但许多大学都有许可证

答案 8 :(得分:1)

将来的某个时间。我认为这很简单。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Median
{
    class Program
    {
        static void Main(string[] args)
        {
            var mediaValue = 0.0;
            var items = new[] { 1, 2, 3, 4,5 };
            var getLengthItems = items.Length;
            Array.Sort(items);
            if (getLengthItems % 2 == 0)
            {
                var firstValue = items[(items.Length / 2) - 1];
                var secondValue = items[(items.Length / 2)];
                mediaValue = (firstValue + secondValue) / 2.0;
            }
            if (getLengthItems % 2 == 1)
            {
                mediaValue = items[(items.Length / 2)];
            }
            Console.WriteLine(mediaValue);
            Console.WriteLine("Enter to Exit!");
            Console.ReadKey();
        }
    }
}

答案 9 :(得分:1)

我有一个带有变量的直方图:group
这是我计算中位数的方法:

int[] group = new int[nbr]; 

// -- Fill the group with values---

// sum all data in median
int median = 0;
for (int i =0;i<nbr;i++) median += group[i];

// then divide by 2 
median = median / 2;

// find 50% first part 
for (int i = 0; i < nbr; i++)
{
   median -= group[i];
   if (median <= 0)
   {
      median = i;
      break;
   }
}

中位数是中位数的组索引

答案 10 :(得分:0)

下面代码有效:但效率不是很高。 :(

static void Main(String[] args) {
        int n = Convert.ToInt32(Console.ReadLine());            
        int[] medList = new int[n];

        for (int x = 0; x < n; x++)
            medList[x] = int.Parse(Console.ReadLine());

        //sort the input array:
        //Array.Sort(medList);            
        for (int x = 0; x < n; x++)
        {
            double[] newArr = new double[x + 1];
            for (int y = 0; y <= x; y++)
                newArr[y] = medList[y];

            Array.Sort(newArr);
            int curInd = x + 1;
            if (curInd % 2 == 0) //even
            {
                int mid = (x / 2) <= 0 ? 0 : (newArr.Length / 2);
                if (mid > 1) mid--;
                double median = (newArr[mid] + newArr[mid+1]) / 2;
                Console.WriteLine("{0:F1}", median);
            }
            else //odd
            {
                int mid = (x / 2) <= 0 ? 0 : (newArr.Length / 2);
                double median = newArr[mid];
                Console.WriteLine("{0:F1}", median);
            }
        }

}

答案 11 :(得分:0)

我的5美分(因为它看起来更简单/更简单,更适合短名单):

public static T Median<T>(this IEnumerable<T> items)
{
    var i = (int)Math.Ceiling((double)(items.Count() - 1) / 2);
    if (i >= 0)
    {
        var values = items.ToList();
        values.Sort();
        return values[i];
    }

    return default(T);
}

P.S。使用ShitalShah所描述的“更高中位数”。

答案 12 :(得分:0)

我认为一个想法正确且快速地起作用:

  1. 获取阵列的平均值
  2. 找到最接近平均值的元素
var avr = array.Average(); 
var median = array.Aggregate((a, b) => Math.Abs(a - avr) < Math.Abs(b - avr) ? a : b);