如何重构此例程以避免使用递归?

时间:2010-08-05 08:26:10

标签: c# algorithm refactoring mergesort

所以我在 C#中编写了一个mergesort作为练习,虽然它有效,但回顾一下代码,还有改进的余地。< / p>

基本上,算法的第二部分需要一个例程来合并两个排序列表

这是我太长的实现,可以使用一些重构:

private static List<int> MergeSortedLists(List<int> sLeft, List<int> sRight)
{
    if (sLeft.Count == 0 || sRight.Count == 0)
    {
        sLeft.AddRange(sRight);
        return sLeft;
    }
    else if (sLeft.Count == 1 && sRight.Count == 1)
    {
        if (sLeft[0] <= sRight[0])
            sLeft.Add(sRight[0]);
        else
            sLeft.Insert(0, sRight[0]);
        return sLeft;
    }
    else if (sLeft.Count == 1 && sRight.Count > 1)
    {
        for (int i=0; i<sRight.Count; i++)
        {
            if (sLeft[0] <= sRight[i])
            {
                sRight.Insert(i, sLeft[0]);
                return sRight;
            }
        }
        sRight.Add(sLeft[0]);
        return sRight;
    }
    else if (sLeft.Count > 1 && sRight.Count == 1)
    {
        for (int i=0; i<sLeft.Count; i++)
        {
            if (sRight[0] <= sLeft[i])
            {
                sLeft.Insert(i, sRight[0]);
                return sLeft;
            }
        }
        sLeft.Add(sRight[0]);
        return sLeft;
    }
    else
    {
        List<int> list = new List<int>();
        if (sLeft[0] <= sRight[0])
        {
            list.Add(sLeft[0]);
            sLeft.RemoveAt(0);
        }
        else
        {
            list.Add(sRight[0]);
            sRight.RemoveAt(0);
        }

        list.AddRange(MergeSortedLists(sLeft, sRight));
        return list;
    }       
}

当然,通过删除递归等可以改善/缩短这个例程。甚至还有其他方法来合并2个排序列表。所以任何重构都是受欢迎的。

虽然我确实有答案,但我很好奇其他程序员将如何改进这个例程。

谢谢!

8 个答案:

答案 0 :(得分:14)

合并两个排序列表可以在O(n)中完成。

List<int> lList, rList, resultList;
int r,l = 0;

while(l < lList.Count && r < rList.Count)
{
  if(lList[l] < rList[r]
    resultList.Add(lList[l++]);
  else
    resultList.Add(rList[r++]);
}
//And add the missing parts.
while(l < lList.Count)
  resultList.Add(lList[l++]);
while(r < rList.Count)
  resultList.Add(rList[r++]);

答案 1 :(得分:4)

我对此的看法是:

private static List<int> MergeSortedLists(List<int> sLeft, List<int> sRight)
{
    List<int> result = new List<int>();
    int indexLeft = 0;
    int indexRight = 0;

    while (indexLeft < sLeft.Count || indexRight < sRight.Count)
    {
        if (indexRight == sRight.Count ||
            (indexLeft < sLeft.Count && sLeft[indexLeft] < sRight[indexRight]))
        {
            result.Add(sLeft[indexLeft]);
            indexLeft++;
        }
        else
        {
            result.Add(sRight[indexRight]);
            indexRight++;
        }
    }
    return result;
}

如果我不得不手动完成,我会怎么做。 =)

答案 2 :(得分:3)

你真的确定你的代码可以运行吗?没有测试,我看到以下内容:

...
else if (sLeft.Count > 1 && sRight.Count == 0)  //<-- sRight is empty
{
    for (int i=0; i<sLeft.Count; i++)
    {
        if (sRight[0] <= sLeft[i]) //<-- IndexError?
        {
            sLeft.Insert(i, sRight[0]);
            return sLeft;
        }
    }
    sLeft.Add(sRight[0]);
    return sLeft;
}
...

答案 3 :(得分:2)

作为一个起点,我会删除您的任何一个列表Count == 1时的特殊情况 - 它们可以通过更一般(当前递归)的情况来处理。

if (sLeft.Count > 1 && sRight.Count == 0) 永远不会为真,因为您在开始时检查了sRight.Count == 0 - 因此永远不会达到此代码并且是多余的。

最后,不是递归(在这种情况下,由于你创建的新列表的数量 - 每个元素一个,这是非常昂贵的!),我会在你的else中做这样的事情(实际上,这个可以取代你的整个方法):

List<int> list = new List<int>();

while (sLeft.Count > 0 && sRight.Count > 0)
{
    if (sLeft[0] <= sRight[0])
    {
        list.Add(sLeft[0]);
        sLeft.RemoveAt(0);
    }
    else
    {
        list.Add(sRight[0]);
        sRight.RemoveAt(0);
    }
}

// one of these two is already empty; the other is in sorted order...
list.AddRange(sLeft);
list.AddRange(sRight);
return list;

(理想情况下,我会重构这个以对每个列表使用整数索引,而不是使用.RemoveAt,因为循环遍历列表比破坏它更有效,并且因为保留原始列表可能有用列表完好无损。但这仍然比原始代码更有效!)

答案 4 :(得分:2)

你也要求采用不同的方法。我可能会根据使用情况做以下操作。下面的代码是懒惰的,因此它不会立即对整个列表进行排序,而只是在请求元素时。

class MergeEnumerable<T> : IEnumerable<T>
    {
        public IEnumerator<T> GetEnumerator()
        {
            var left = _left.GetEnumerator();
            var right = _right.GetEnumerator();
            var leftHasSome = left.MoveNext();
            var rightHasSome = right.MoveNext();
            while (leftHasSome || rightHasSome)
            {
                if (leftHasSome && rightHasSome)
                {
                  if(_comparer.Compare(left.Current,right.Current) < 0)
                  {
                    yield return returner(left);
                  } else {
                    yield return returner(right);
                  }
                }
                else if (rightHasSome)
                {
                    returner(right);
                }
                else
                {
                    returner(left);
                }
            }
        }

        private T returner(IEnumerator<T> enumerator)
        {
            var current = enumerator.Current;
            enumerator.MoveNext();
            return current;
        }

        System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
        {
            return ((IEnumerable<T>)this).GetEnumerator();
        }

        private IEnumerable<T> _left;
        private IEnumerable<T> _right;
        private IComparer<T> _comparer;

        MergeEnumerable(IEnumerable<T> left, IEnumerable<T> right, IComparer<T> comparer)
        {
            _left = left;
            _right = right;
            _comparer = comparer;
        }
    }

编辑:这与Sergey Osypchuk的实施基本相同,他的意志从开始到结束只看排序最快但延迟也会更高,因为整个整理的事实提前列出。所以正如我所说,根据使用情况,我可能采用这种方法,另一种选择类似于Sergey Osypchuk

答案 5 :(得分:2)

通常你可以使用堆栈而不是使用递归

答案 6 :(得分:1)

合并列表(理论上,输入列表是预先排序的)排序可以通过以下方式实现:

List<int> MergeSorting(List<int> a, List<int> b)
    {
        int apos = 0;
        int bpos = 0;
        List<int> result = new List<int>();
        while (apos < a.Count && bpos < b.Count)
        {
            int avalue = int.MaxValue;
            int bvalue = int.MaxValue;
            if (apos < a.Count)
                avalue = a[apos];
            if (bpos < b.Count)
                bvalue = b[bpos];
            if (avalue < bvalue)
            {
                result.Add(avalue);
                apos++;
            }
            else
            {
                result.Add(bvalue);
                bpos++;
            }
        }
        return result;
    }

如果您从未排序的列表开始,您需要按排序后的子序列拆分它,而不是使用上面的函数将它们分开

答案 7 :(得分:0)

我从不使用递归进行合并排序。您可以对输入进行迭代传递,利用每个合并传递的排序块大小加倍的事实。跟踪每个输入列表中的块大小和已处理项目的数量;当它们相等时,列表就会用尽。当两个列表都耗尽时,您可以继续前进到下一对块。当块大小大于或等于您的输入大小时,您就完成了。

编辑:由于我的误解,我之前留下的一些信息不正确 - C#中的List类似于数组而不是链表。道歉。