所以我在 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个排序列表。所以任何重构都是受欢迎的。
虽然我确实有答案,但我很好奇其他程序员将如何改进这个例程。
谢谢!
答案 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类似于数组而不是链表。道歉。