【问题标题】:Sorting a linked list对链表进行排序
【发布时间】:2010-10-20 13:34:52
【问题描述】:

我用 C# 编写了一个基本的链表类。它有一个 Node 对象,它(显然)代表列表中的每个节点。

代码没有使用IEnumerable,但是,我可以实现排序功能吗?我使用的语言是 C#。 C#中有这样的例子吗?

我正在使用这个sample

谢谢

【问题讨论】:

    标签: c# algorithm data-structures linked-list


    【解决方案1】:

    当然,您可以使用普通链表实现排序功能。 Merge sort 可能是一个适合尝试的算法,它相当简单。

    【讨论】:

    • +1 用于建议合并排序,非常适合链表。
    【解决方案2】:

    最简单的选择可能是提取数据,在已经支持排序的机制中排序(数组,List<T>IEnumerable<T> 通过 LINQ),然后用排序后的数据重新构建链表。

    如果您想编写自己的排序算法,那么您可能会发现Comparer<T>.Default 很有用(假设您使用的是泛型)。这应该允许您比较 IComparable<T>IComparable 的任何项目。

    顺便说一句 - 请注意 .NET 已经包含 LinkedList<T> 等;如果这只是为了学习等,那很好;-p

    【讨论】:

    • 对不起,伙计,我不得不投票给你。我希望曾经实现过自己的链表的人这样做是有原因的(想不出一个但谁知道)。我不得不在一次采访中这样做,我怀疑说使用 STL 就足够了。
    • 好吧...有点怪异 - 4 周前的回复在 2 天内投了 3 次反对票?发生了一些可疑的事情......
    • 我支持我对 baash05 的回复:关于您的评论“我怀疑说使用 STL 就足够了” - 在面试时,我会质疑任何没有的人的理智i> 没有充分理由选择使用预装 BCL。 OP 没有说明原因,因此 BCL 应该是默认值。
    • 我试图投票支持 Marc...尽管 Marc 假设您来到一个在本地实现了链接列表的项目并且您必须对其进行排序。将其移入 STL 然后退出以便应用程序的其余部分可以使用它会对资源造成巨大压力,更不用说内存了。问题没有说不要使用,但也没有说不要将其发送到印度并让其他人也这样做。它只是问如何,你没有说如何。我仍然不同意答案的大否定。我讨厌答案的否定值。他们否定了努力,我告诉你我感谢所有“我们”的努力。
    【解决方案3】:

    功能快速排序和合并排序

    这是一个以函数式编写的带有快速排序和合并排序方法的链表:

    class List
    {
        public int item;
        public List rest;
    
        public List(int item, List rest)
        {
            this.item = item;
            this.rest = rest;
        }
    
        // helper methods for quicksort
    
        public static List Append(List xs, List ys)
        {
            if (xs == null) return ys;
            else return new List(xs.item, Append(xs.rest, ys));
        }
    
        public static List Filter(Func<int,bool> p, List xs)
        {
            if (xs == null) return null;
            else if (p(xs.item)) return new List(xs.item, Filter(p, xs.rest));
            else return Filter(p, xs.rest);
        }
    
        public static List QSort(List xs)
        {
            if (xs == null) return null;
            else
            {
                int pivot = xs.item;
                List less = QSort(Filter(x => x <= pivot, xs.rest));
                List more = QSort(Filter(x => x > pivot, xs.rest));
                return Append(less, new List(pivot, more));
            }
        }
    
        // Helper methods for mergesort
    
        public static int Length(List xs)
        {
            if (xs == null) return 0;
            else return 1 + Length(xs.rest);
        }
    
        public static List Take(int n, List xs)
        {
            if (n == 0) return null;
            else return new List(xs.item, Take(n - 1, xs.rest));
        }
    
        public static List Drop(int n, List xs)
        {
            if (n == 0) return xs;
            else return Drop(n - 1, xs.rest);
        }
    
        public static List Merge(List xs, List ys)
        {
            if (xs == null) return ys;
            else if (ys == null) return xs;
            else if (xs.item <= ys.item) return new List(xs.item, Merge(xs.rest, ys));
            else return new List(ys.item, Merge(xs, ys.rest));
        }
    
        public static List MSort(List xs)
        {
            if (Length(xs) <= 1) return xs;
            else
            {
                int len = Length(xs) / 2;
                List left  = MSort(Take(len, xs));
                List right = MSort(Drop(len, xs));
                return Merge(left, right);
            }
        }
    
        public static string Show(List xs)
        {
            if(xs == null) return "";
            else return xs.item.ToString() + " " + Show(xs.rest);
        }
    }
    

    使用配对堆的函数堆排序

    奖励:堆排序(使用功能配对堆)。

    class List
    {
        // ...
    
        public static Heap List2Heap(List xs)
        {
            if (xs == null) return null;
            else return Heap.Merge(new Heap(null, xs.item, null), List2Heap(xs.rest));
        }
    
        public static List HSort(List xs)
        {
            return Heap.Heap2List(List2Heap(xs));
        }
    }
    
    class Heap
    {
        Heap left;
        int min;
        Heap right;
    
        public Heap(Heap left, int min, Heap right)
        {
            this.left = left;
            this.min = min;
            this.right = right;
        }
    
        public static Heap Merge(Heap a, Heap b)
        {
            if (a == null) return b;
            if (b == null) return a;
    
            Heap smaller = a.min <= b.min ? a : b;
            Heap larger = a.min <= b.min ? b : a;
            return new Heap(smaller.left, smaller.min, Merge(smaller.right, larger));
        }
    
        public static Heap DeleteMin(Heap a)
        {
            return Merge(a.left, a.right);
        }
    
        public static List Heap2List(Heap a)
        {
            if (a == null) return null;
            else return new List(a.min, Heap2List(DeleteMin(a)));
        }
    }
    

    对于实际使用,您希望在不使用递归的情况下重写辅助方法,并且可能使用像内置列表这样的可变列表。

    使用方法:

    List xs = new List(4, new List(2, new List(3, new List(1, null))));
    Console.WriteLine(List.Show(List.QSort(xs)));
    Console.WriteLine(List.Show(List.MSort(xs)));
    Console.WriteLine(List.Show(List.HSort(xs)));
    

    用于链表的命令式就地快速排序

    请求了就地版本。这是一个非常快速的实现。我从上到下编写了这段代码,没有寻找机会让代码变得更好,即每一行都是我想到的第一行。它非常难看,因为我使用 null 作为空列表 :) 缩进不一致等。

    此外,我仅在一个示例中测试了此代码:

            MList ys = new MList(4, new MList(2, new MList(3, new MList(1, null))));
            MList.QSortInPlace(ref ys);
            Console.WriteLine(MList.Show(ys));
    

    神奇的是,它第一次成功了!我很确定这段代码包含错误。不要追究我的责任。

    class MList
    {
        public int item;
        public MList rest;
    
        public MList(int item, MList rest)
        {
            this.item = item;
            this.rest = rest;
        }
    
        public static void QSortInPlace(ref MList xs)
        {
            if (xs == null) return;
    
            int pivot = xs.item;
            MList pivotNode = xs;
            xs = xs.rest;
            pivotNode.rest = null;
            // partition the list into two parts
            MList smaller = null; // items smaller than pivot
            MList larger = null; // items larger than pivot
            while (xs != null)
            {
                var rest = xs.rest;
                if (xs.item < pivot) {
                    xs.rest = smaller;
                    smaller = xs;
                } else {
                    xs.rest = larger;
                    larger = xs;
                }
                xs = rest;
            }
    
            // sort the smaller and larger lists
            QSortInPlace(ref smaller);
            QSortInPlace(ref larger);
    
            // append smaller + [pivot] + larger
            AppendInPlace(ref pivotNode, larger);
            AppendInPlace(ref smaller, pivotNode);
            xs = smaller;
        }
    
        public static void AppendInPlace(ref MList xs, MList ys)
        {
            if(xs == null){
                xs = ys;
                return;
            }
    
            // find the last node in xs
            MList last = xs;
            while (last.rest != null)
            {
                last = last.rest;
            }
            last.rest = ys;
        }
    
        public static string Show(MList xs)
        {
            if (xs == null) return "";
            else return xs.item.ToString() + " " + Show(xs.rest);
        }
    }
    

    【讨论】:

    • 我不确定它是如何工作的。我确实知道快速排序在理论上是如何工作的,但我不确定这段代码。你能给我举个例子,你在列表上使用快速排序,它会在完成后显示排序列表吗?
    • 我添加了一个例子。这种快速排序的工作原理如下:选择第一个项目作为枢轴。然后将剩余的项目拆分为两个列表:更少和更多。 Less 包含所有小于或等于枢轴的项目,而更多包含其余的(大于枢轴的项目)。下一步是分别对这两个列表进行排序。完成后,我们从三个部分构建一个列表,依次为 less+pivot+more。
    • 我看高又低,在您的 qsort 函数中找不到“x”的定义。你能编辑一下吗?我觉得很有趣。
    • 另外.. 你能在不使用 new 的情况下做这些事情吗?在一个记忆紧张的世界里,你将如何完成这些壮举?
    • 您可以就地进行快速排序和合并排序。
    【解决方案4】:

    这可能不是最好的解决方案,但它就像我能想到的一样简单。如果有人能想到一些更简单但仍然很快的东西,我很想听听。
    抱歉,它应该翻译成 C++。

    List * SortList(List * p_list)
    {
         if(p_list == NULL || p_list->next == NULL) 
              return p_list;
    
         List left, right;
         List * piviot = p_list;
         List * piviotEnd = piviot;
         List * next = p_list->next;
         do
         {
                  p_list = next;
                  next = next->next;
                  //FIGURE OUT WHICH SIDE I'M ADDING TO.
                  if(p_list->data > piviot->data ) 
                      right.InsertNode(p_list);
                  else if(p_list->data < piviot->data)
                      left.InsertNode(p_list);
                  else
                  {   //we put this in it's own list because it doesn't need to be sorted
                      piviotEnd->next = p_list;
                      piviotEnd= p_list;
                  }  
         }while(next);
    
         //now left contains values < piviot and more contains all the values more
         left.next = SortList(left.next);
         right.next = SortList(right.next);
    
         //add the piviot to the list then add the rigth list to the full list
         left.GetTail()->next = piviot;
         piviotEnd->next = right.next;
    
         return left.next;
    }
    

    【讨论】:

    • 回复您的评论“我怀疑说使用 STL 就足够了” - 在面试时,我会质疑任何没有选择使用 pre-没有充分理由的罐装 BCL。 OP 没有说明原因,因此 BCL 应该是默认值。
    • 同意。专业 100% 与您同在。我从没想过要实现一个链表。我也会在采访中指出这一点(我被问到了)。他们通常希望看到你出汗,所以你仍然必须实施它。
    • 现在好了.. 在这里我不同意自己。 --- 我正在编写无法访问 STL 的代码。所以我需要自己创建一个排序器。很高兴我写了这个答案,所以我将使用它。:)
    【解决方案5】:
    for(int i=0; i<counter;i++)
    {
      while(current.next!=null)
      {
        if(current.elemen>current.next.element)
        {
          Swap(current.elment,current.next.element);
        }
        current=current.next;
      }
    }
    

    向链表添加或插入元素时递增计数器

    【讨论】:

      【解决方案6】:

      如果您想真正利用您使用的是链表这一事实,而不是解决它,我建议您使用插入排序。

      通常插入排序效率不高 - 在最坏的情况下是 O(n^2),但使用链表可以将其改进为 O(n log n)

      伪:

      for i in range (1,n):
          item = arr[i]
          location = binary_search(l=root, r=i, value=item.val)  // O(log i)
          insert_item_before(arr[location], item) // O(1)
      

      在常规算法中,插入部分采用O(i),因为我们可能需要移动所有大于item 的项目。因为链表可以让我们在不移位的情况下进行插入,所以我们可以通过二分查找来查找位置,所以插入部分只需要O(log i)

      注意:通常插入排序在最佳情况下具有O(n) 性能(如果数组已排序)。不幸的是,这里不是这种情况,因为二进制搜索总是需要O(log n) 步骤。

      【讨论】:

        【解决方案7】:
        public LinkedListNode<int> Sort2(LinkedListNode<int> head, int count)
            {
                var cur = head;
                var prev = cur;
                var min = cur;
                var minprev = min;
        
                LinkedListNode<int> newHead = null;
                LinkedListNode<int> newTail = newHead;
        
                for (int i = 0; i < count; i++)
                {
                    cur = head;
                    min = cur;
                    minprev = min;
        
                    while (cur != null)
                    {
                        if (cur.Value < min.Value)
                        {
                            min = cur;
                            minprev = prev;
                        }
                        prev = cur;
                        cur = cur.Next;
                    }
        
                    if (min == head)
                        head = head.Next;
                    else if (min.Next == null)
                        minprev.Next = null;
                    else
                        minprev.Next = minprev.Next.Next;
        
                    if (newHead != null)
                    {
                        newTail.Next = min;
                        newTail = newTail.Next;
                    }
                    else
                    {
                        newHead = min;
                        newTail = newHead;
                    }
                }
                return newHead;
            }
        

        【讨论】:

          【解决方案8】:

          有些人(包括我)可能想要排序LinkedList&lt;T&gt; from .net library.

          简单的方法是使用Linq,排序并最终创建一个新的链表。但这会产生垃圾。对于小型收藏,这不是问题,但对于大型收藏,它可能没有那么高效。

          对于想要进行某种程度优化的人,这里是 .net 双向链表的通用就地快速排序实现。

          此实现不拆分/合并,而是检查节点的每个递归的边界。

          /// <summary>
          /// in place linked list sort using quick sort.
          /// </summary>
          public static void QuickSort<T>(this LinkedList<T> linkedList, IComparer<T> comparer)
          {
              if (linkedList == null || linkedList.Count <= 1) return; // there is nothing to sort
              SortImpl(linkedList.First, linkedList.Last, comparer);
          }
          
          private static void SortImpl<T>(LinkedListNode<T> head, LinkedListNode<T> tail, IComparer<T> comparer)
          {
              if (head == tail) return; // there is nothing to sort
          
              void Swap(LinkedListNode<T> a, LinkedListNode<T> b)
              {
                  var tmp = a.Value;
                  a.Value = b.Value;
                  b.Value = tmp;
              }
          
              var pivot = tail;
              var node = head;
              while (node.Next != pivot)
              {
                  if (comparer.Compare(node.Value, pivot.Value) > 0)
                  {
                      Swap(pivot, pivot.Previous);
                      Swap(node, pivot);
                      pivot = pivot.Previous; // move pivot backward
                  }
                  else node = node.Next; // move node forward
              }
              if (comparer.Compare(node.Value, pivot.Value) > 0)
              {
                  Swap(node, pivot);
                  pivot = node;
              }
          
              // pivot location is found, now sort nodes below and above pivot.
              // if head or tail is equal to pivot we reached boundaries and we should stop recursion.
              if (head != pivot) SortImpl(head, pivot.Previous, comparer);
              if (tail != pivot) SortImpl(pivot.Next, tail, comparer);
          }
          

          【讨论】:

          • 是否具有相同的时间复杂度:最佳 nlog(n),平均:nlog(n),最差:n^2 和空间复杂度 log(n)作为数组的快速排序?
          • 它会为大列表抛出堆栈溢出异常。
          猜你喜欢
          • 2011-12-26
          • 1970-01-01
          • 2010-09-14
          • 2017-03-22
          • 2021-02-01
          • 2020-09-20
          • 2012-12-25
          相关资源
          最近更新 更多