有没有一种有效的算法可以做到这一点?

时间:2015-04-09 03:49:30

标签: algorithm language-agnostic

我有两个长度相等的整数列表,每个都没有重复,我需要根据差异的(绝对值)将它们相互映射,在输出中没有任何东西可以切换到总计所有对的差异较小。天真的'我能想到的方法就是这样(在简化的C#中,但我觉得它很容易获得):

Dictionary<int, int> output;
List<int> list1, list2;
while(!list1.Empty) //While we haven't arranged all the pairs
{
    int bestDistance = Int32.MaxValue; //best distance between numbers so far
    int bestFirst, bestSecond; //best numbers so far
    foreach(int i in list1)
    {
        foreach(int j in list2)
        {
            int distance = Math.Abs(i - j);
            //if the distance is better than the best so far, make it the new best
            if(distance < bestDistance)
            {
                bestDistance = distance;
                bestFirst = i;
                bestSecond = j;
            }
        }
    }
    output[bestFirst] = bestSecond; //add the best to dictionary
    list1.Remove(bestFirst); //remove it from the lists
    list2.Remove(bestSecond);
}

基本上,它只是找到最好的一对,删除它,然后重复直到它完成。但如果我正确看到它,这将在立方时间内运行,并且对于大型列表将花费相当长的时间。有没有更快的方法呢?

1 个答案:

答案 0 :(得分:1)

这比我最初的预感所暗示的要简单得多。保持此O(N log(N))的关键是使用排序列表,并在第二个排序列表中搜索“pivot”元素,与第一个排序列表中的第一个元素的差异最小。

因此采取的步骤成为:

  1. 对两个输入列表进行排序
  2. 在第二个排序列表中找到pivot元素
  3. 将此pivot元素与第一个已排序列表的第一个元素一起返回
  4. 跟踪左侧枢轴和右侧枢轴
  5. 的元素索引
  6. 按排序顺序迭代第一个列表,返回左侧或右侧元素,具体取决于哪个差异最小,并调整左右索引。
  7. 如(c#示例):

    public static IEnumerable<KeyValuePair<int, int>> FindSmallestDistances(List<int> first, List<int> second)
    {
        Debug.Assert(first.Count == second.Count); // precondition.
    
        // sort the input: O(N log(N)).
        first.Sort();
        second.Sort();
    
        // determine pivot: O(N).
        var min_first = first[0];
        var smallest_abs_dif = Math.Abs(second[0] - min_first);
        var pivot_ndx = 0;
        for (int i = 1; i < second.Count; i++)
        {
            var abs_dif = Math.Abs(second[i] - min_first);
            if (abs_dif < smallest_abs_dif)
            {
                smallest_abs_dif = abs_dif;
                pivot_ndx = i;
            }
        };
    
        // return the first one.
        yield return new KeyValuePair<int, int>(min_first, second[pivot_ndx]);
    
        // Iterate the rest: O(N)
        var left = pivot_ndx - 1;
        var right = pivot_ndx + 1;
        for (var i = 1; i < first.Count; i++)
        {
            if (left >= 0)
            {
                if (right < first.Count && Math.Abs(first[i] - second[left]) > Math.Abs(first[i] - second[right]))
                    yield return new KeyValuePair<int, int>(first[i], second[right++]);
                else
                    yield return new KeyValuePair<int, int>(first[i], second[left--]);
            }
            else
                yield return new KeyValuePair<int, int>(first[i], second[right++]);
        }
    }