【问题标题】:quicksort on singly-linked list单链表上的快速排序
【发布时间】:2011-07-25 09:12:59
【问题描述】:

我的快速排序不起作用。我特别不确定要传递给分区算法的内容以及如何管理枢轴,因为在一种情况下它成为头节点,在另一种情况下成为最后一个节点。我的方法基于数​​组的解决方案。这是我的尝试。有任何想法吗?请注意,选择分区算法是为了适应单向链表 (SLL) 的单向特性。

public static SLL quickSort(SLL list, SLLNode first, SLLNode last)
{
    if (first != null && last != null)
    {
        SLLNode p = partition(list, first, last) ;
        quickSort(list,first,p) ;
        quickSort(list,p.succ, last) ;
    }
    return list ;
}

public static SLLNode partition(SLL list, SLLNode first, SLLNode last)
{
    //last.succ = null ;
    SLLNode p = first ;
    SLLNode ptr = p.succ ;

    while (ptr!=null)
    {
        if (ptr.data.compareToIgnoreCase(p.data)<0)
        {
            String pivot = p.data ;
            p.data =  ptr.data ;
            ptr.data = p.succ.data ;
            p.succ.data = pivot ;
            p = p.succ ;
        }
        ptr = ptr.succ ;
    }
    return p ;
}

[编辑]

  • 我想“就地”执行此操作

  • 我正在寻求有关如何在此过程中管理头部和尾部的帮助。

  • 除非我的方法不可能,否则请不要提出替代方案

【问题讨论】:

  • 当您在调试器中单步执行代码时,您会看到什么?您可以拥有的最简单但不起作用的示例是什么?你能提供一个演示问题的单元测试吗?
  • 您想要对单链表进行就地排序?这会很慢。如果你可以用一些内存来换取时间和清晰的算法,那么让分区实际上创建两个新的子列表,对它们进行递归排序并连接结果。
  • 我认为“last.succ = null”会破坏链表。
  • @all, "last.succ = null" 现在被注释掉了——这只是我试图找到一种方法来处理传入的 head 和 last 的另一种尝试。
  • 只是为了确定 - 是否有任何具体原因您没有使用更合适的算法,例如 Mergesort?

标签: java algorithm singly-linked-list


【解决方案1】:
//logic - from left to right, remove smaller node and append at the beginning,
public void partition(int x)// x is your pivot element.
        {
            Node prev = null;
            Node cur = root;
            while (cur!=null)
            {
                if ( cur.data > x || cur == root)
                {
                    cur = cur.next;
                    prev = cur;
                }
                else
                {
                    Node next = cur.next;
                    if (prev != null) prev.next = next;
                    cur.next = root;
                    root = cur;
                    cur = next;
                }
            }
        }

【讨论】:

    【解决方案2】:

    对链表进行快速排序的常规方法是在分区步骤中创建两个(或者最好是三个,中间的一个是所有元素都等于枢轴元素)新列表,递归地对第一个和最后一个进行排序,然后将它们连接在一起。

    您的代码反而会交换节点内的数据...有趣。我尝试了以下(子)列表:

            first                            last
            [ 5 ]-->[ 3 ]-->[ 7 ]-->[ 9 ]-->[ 2 ]
    

    看看你的算法做了什么:

            [ 5 ]-->[ 3 ]-->[ 7 ]-->[ 9 ]-->[ 2 ]
              p
                     ptr
    
    3<5? (yes) {
    pivot=5
            [ 3 ]-->[ 3 ]-->[ 7 ]-->[ 9 ]-->[ 2 ]
              p      ptr
            [ 3 ]-->[ 7 ]-->[ 7 ]-->[ 9 ]-->[ 2 ]
              p      ptr
            [ 3 ]-->[ 7 ]-->[ 5 ]-->[ 9 ]-->[ 2 ]
              p      ptr
            [ 3 ]-->[ 7 ]-->[ 5 ]-->[ 9 ]-->[ 2 ]
                      p
                     ptr
    }
            [ 3 ]-->[ 7 ]-->[ 5 ]-->[ 9 ]-->[ 2 ]
                      p
                             ptr
    

    这是第一次迭代后的状态。从ptr != null 开始,我们现在进行下一次迭代,但我想你已经可以在这里看到问题了。在下一次迭代之后它看起来像这样:

            [ 3 ]-->[ 5 ]-->[ 9 ]-->[ 7 ]-->[ 2 ]
                              p
                                     ptr
    

    在下一次迭代中不会发生交换:

            [ 3 ]-->[ 5 ]-->[ 9 ]-->[ 7 ]-->[ 2 ]
                              p
                                             ptr
    

    现在我们将得到一个 NullPointerException,因为 ptr.next == null(或者如果这是一个更大列表的子列表,我们将交换超出限制)。

    所以,一些想法:

    • 在交换步骤中,您应该确保之后 p 指向现在包含枢轴元素的节点。
    • 您应该在到达last 元素时停下来,并确保不要在它之后进行交换(但如果需要,元素本身仍会被交换)。

    【讨论】:

      【解决方案3】:

      对于非破坏性版本,将以下伪代码转换为 Java:

      quickSort list
          | list.isEmpty = empty list
          | otherwise = concat (quickSort lower) (new List(pivot, quickSort upper))
          where
               pivot = list.head
               lower = list of elements from list.tail that are < pivot
               upper = list of elements from list.tail that are >= pivot
      

      【讨论】:

        【解决方案4】:

        您可能想查看“last.succ = null”这一行。我认为您可能希望找到其他方法来检查您是否已到达要分区的列表段的末尾。事实上,我认为你应该得到一个空指针异常,但我可能弄错了。

        【讨论】: