Pergunta

Então eu estava escrevendo um mesclar dentro C# Como um exercício E embora tenha funcionado, olhando para o código, havia espaço para melhorias.

Basicamente, a segunda parte do algoritmo requer uma rotina para mesclar duas listas classificadas.

Aqui está o meu jeito de implementação muito longa que poderia usar algumas refatoramentos:

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;
    }       
}

Certamente essa rotina pode ser melhorada/reduzida removendo recursão, etc. Existem até outras maneiras de mesclar 2 listas classificadas. Então algum A refatoração é bem -vinda.

Embora eu tenha uma resposta, estou curioso sobre como outros programadores iriam melhorar essa rotina.

Obrigada!

Foi útil?

Solução

Como ponto de partida, eu removeria seus casos especiais para quando qualquer uma das listas tiver Count == 1 - Eles podem ser tratados pelo seu caso mais geral (atualmente com recurso).

o if (sLeft.Count > 1 && sRight.Count == 0) vai Nunca seja verdade porque você verificou sRight.Count == 0 No início - portanto, esse código nunca será alcançado e é redundante.

Finalmente, em vez de se repetir (o que é muito caro neste caso devido ao número de novas listas que você cria - uma por elemento!), Eu faria algo assim em seu else (Na verdade, isso pode substituir todo o seu método):

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;

(Idealmente, eu refatoraria isso para usar índices inteiros em cada lista, em vez de usar .RemoveAt, porque é mais executado percorrer a lista do que destruí -la e porque pode ser útil deixar intactas as listas originais. Este é um código ainda mais eficiente do que o original!)

Outras dicas

A fusão de duas listas classificadas pode ser feita em 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++]);

Minha opinião sobre isso seria:

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;
}

Exatamente o que eu faria se tivesse que fazer à mão. =)

Você tem certeza de que seu código funciona? Sem testá -lo, vejo o seguinte:

...
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;
}
...

Você estava pedindo abordagens diferentes também. Eu posso fazer o mais abaixo, dependendo do uso. O código abaixo é preguiçoso para que não classifique a lista inteira de uma só vez, mas apenas quando os elementos forem solicitados.

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;
        }
    }

EDITAR: É basicamente a mesma implementação que Sergey Osypchuk, sua vontade do início ao fim quando se destacar apenas para a classificação, mas a latência será maior, devido ao fato de classificar toda a lista antecipada. Então, como eu disse, dependendo do uso, eu posso ir com essa abordagem e uma alternativa seria algo semelhante a Sergey Osypchuk

Muitas vezes você pode usar uma pilha em vez de usar recursão

Lista de mesclagem (por teoria, listas de entrada são classificadas com antecedência) A classificação pode ser implementada da seguinte maneira:

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;
    }

Caso você comece com a lista não classificada, você precisa dividi -la por subsequência classificada e, emo

Eu nunca uso a recursão para a classificação de mesclagem. Você pode fazer passes iterativos sobre a entrada, aproveitando o fato de que o tamanho do bloco classificado dobra a cada passe de fusão. Acompanhe o tamanho do bloco e a contagem de itens que você processou em cada lista de entrada; Quando eles são iguais, a lista está esgotada. Quando as duas listas estão esgotadas, você pode passar para o próximo par de blocos. Quando o tamanho do bloco é maior ou igual ao seu tamanho de entrada, você terminou.

Editar: Algumas das informações que eu deixei anteriormente estavam incorretas, devido ao meu mal -entendido - uma lista em C# é semelhante a uma matriz e não uma lista vinculada. Me desculpe.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top