在具有非不同元素的数组中查找局部最小值

时间:2015-04-30 16:43:51

标签: c# arrays

输入数组类似于:

[0,0,1,2,4,7,9,6,4,4,2,1,0,0,0,0,0,3,5,5,10,3,2,1,4,5,7,7,12,11,8,4,2,
 1,1,1,2,4,9,4,2,2,0,1,3,6,13,11,5,5,2,2,3,4,7,11,8...]

正如我们所看到的,阵列中有丘陵和山谷,同一个山谷的重复(可能)最小值。我将This(具有不同元素的数组)想法扩展到上面的解决方案:

/// <summary>
/// Returns index array where valley lies
/// </summary>
/// <param name="arraySmoothed"></param>
/// <returns></returns>
private static List<int> GetValleys(List<int> arraySmoothed) {
    List<int> valleys = new List<int>();
    List<int> tempValley = new List<int>();

    bool contdValleyValues = false;
    for (int i = 0; i < arraySmoothed.Count; i++) {
        // A[i] is minima if A[i-1] >= A[i] <= A[i+1], <= instead of < is deliberate otherwise it won't work for consecutive repeating minima values for a valley
        bool isValley = ((i == 0 ? -1 : arraySmoothed[i - 1]) >= arraySmoothed[i]) 
            && (arraySmoothed[i] <= (i == arraySmoothed.Count - 1 ? -1 : arraySmoothed[i + 1]));

        // If several equal minima values for same valley, average the indexes keeping in temp list
        if (isValley) {
            if (!contdValleyValues)
                contdValleyValues = true;
            tempValley.Add(i);
        } else  {
            if (contdValleyValues) {
                valleys.Add((int)tempValley.Average());
                tempValley.Clear();
                contdValleyValues = false;
            }
        }
    }
    return valleys;
}

这种方法陷入了... 7,9,6,4,4,2,1,0,0,0,0,0,3,5,5,10,3 ...它抛出的地方三个最小值,但应该有一个(五个中间的0)。复杂性对我来说不是问题,会崇拜 O(n)。我只想要一个通用的解决方案。任何提示/帮助将不胜感激。

2 个答案:

答案 0 :(得分:6)

您可以使用一个简单的状态机对其进行编码,其中三个状态代表曲线的最近历史记录:

  • 走下去
  • 下降后的等距
  • 不下去(即上升后上升或平躺)

状态转换图如下所示:

State transition diagram

图片看起来有点复杂,但描述很简单: - 当曲线向上倾斜时,下一个状态为NotGoingDown - 当曲线向下倾斜时,下一个状态为GoingDown - 当拉伸为水平时,如果曲线下降,则状态变为EqGoingDown;如果曲线变平或变高,状态变为NotGoingDown

以下是C#中的实现:

enum CurveState {
    GoingDown=0, EqGoingDown=1, NotGoingDown=2
}
private static IList<int> GetValleys(IList<int> a) {
    var res = new List<int>();
    if (a.Count < 2) {
        return res;
    }
    int lastEq = 0;
    CurveState s = CurveState.NotGoingDown;
    for (var i = 1 ; i != a.Count ; i++) {
        switch(Math.Sign(a[i]-a[i-1])) {
            case -1:
                s = CurveState.GoingDown;
                break;
            case  0:
                if (s == CurveState.GoingDown) {
                    lastEq = i;
                }
                s = (s==CurveState.NotGoingDown)
                  ? CurveState.NotGoingDown
                  : CurveState.EqGoingDown;
                break;
            case 1:
                if (s == CurveState.GoingDown) {
                    res.Add(i-1);
                } else if (s == CurveState.EqGoingDown) {
                    res.Add((lastEq+i-1)/2);
                }
                s = CurveState.NotGoingDown;
            break;
        }
    }
    return res;
}

Demo使用您的数字,用星号标记山谷。

lastEq变量是当前相等范围开始的位置的索引。请注意,转换表的设置方式是{I}处于lastEq状态时始终设置CurveState.EqGoingDown(lastEq+i-1)/2公式计算值相等的最后一个位置与i-1之间的平均值。

答案 1 :(得分:2)

问题在于,在持续的山谷结束时,您需要检查下一个值是否较低,这意味着您实际上并不在山谷的底部。此外,您必须跟踪平坦区域之前的坡度,以确定您是否在平坦区域之前上升或下降。另外,您不需要contdValleyValues,因为您只需检查tempValley,看看它是否有任何值。

private static List<int> GetValleys(List<int> arraySmoothed)
{
    List<int> valleys = new List<int>();
    List<int> tempValley = new List<int>();
    int slope = 0;
    for (int i = 1; i < arraySmoothed.Count - 1; i++)
    {
        // A[i] is minima if A[i-1] >= A[i] <= A[i+1], <= instead of < is deliberate 
        // otherwise it won't work for consecutive repeating minima values for a 
        // valley
        bool isValley = arraySmoothed[i - 1] >= arraySmoothed[i] 
                     && arraySmoothed[i] <= arraySmoothed[i + 1];

        // If several equal minima values for same valley, average the indexes 
        // keeping in temp list
        if (isValley)
        {
            tempValley.Add(i);
        }
        else
        {
            if (tempValley.Any())
            {
                if (arraySmoothed[i - 1] < arraySmoothed[i] && slope == -1)
                {
                    valleys.Add((int)tempValley.Average());
                }

                tempValley.Clear();
            }
        }

        if (arraySmoothed[i - 1] > arraySmoothed[i])
        {
            slope = -1;
        }
        else if (arraySmoothed[i - 1] < arraySmoothed[i])
        {
            slope = 1;
        }
    }

    return valleys;
}