实施荷兰国旗计划与链接列表

时间:2013-06-20 01:13:03

标签: algorithm data-structures linked-list dutch-national-flag-problem

我想对包含0,1s或2s的链表进行排序。现在,这显然是荷兰国旗问题的一个变种。 http://en.wikipedia.org/wiki/Dutch_national_flag_problem

与链接中给出的算法相同的算法是:

“让顶层组从阵列顶部向下生长,底部组从底部长大,并保持中间组位于底部之上。算法将位置存储在顶层组的正下方,就在在每个步骤中,检查中间上方的元素。如果它属于顶部组,则将其与顶部下方的元素交换。如果它属于底部,则交换它如果它位于中间,则保留它。更新相应的索引。复杂性是Θ(n)移动和检查。“

为此提供的C ++实现是:

void threeWayPartition(int data[], int size, int low, int high) {
  int p = -1;
  int q = size;
  for (int i = 0; i < q;) {
    if (data[i] == low) {
      swap(data[i], data[++p]);
      ++i;
    } else if (data[i] >= high) {
      swap(data[i], data[--q]);
    } else {
      ++i;
    }
  }

}

我唯一的问题是我们如何在链表中遍历,就像我们在数组中一样?

5 个答案:

答案 0 :(得分:4)

标准的单链表不允许您在给定链表单元格的情况下向后移动。但是,您可以使用双向链表,其中每个单元存储下一个和前一个指针。这样可以让你向前和向后浏览列表。

但是,对于您尝试解决的特定问题,我认为这不是必要的。数组和链表上的算法之间的一个主要区别是,在使用链表时,您可以重新排列列表中的单元格以重新排序列表中的元素。因此,您在上面详述的算法 - 通过更改数组的内容来工作 - 实际上可能不是链接列表上最优雅的算法。

如果您确实使用链接列表,解决此问题的一种可能方法如下:

  • 创建包含0,1或2的所有值的列表。
  • 从链接列表中删除所有单元格,并将它们分发到等于0,1或2的元素列表中。
  • 将这三个列表连接在一起。

这不需要内存分配,只需重新排列链表单元格即可。它仍然在时间Θ(n)中运行,这是另一个加号。此外,你可以做到这一点,而不必向后走(即这适用于单链表)。

我将把完整的实现留给你,但作为一个例子,这里是简单的C ++代码,用于将链表单元格分配到零个,一个和两个列表中:

struct Cell {
    int value;
    Cell* next;
}

/* Pointers to the heads of the three lists. */
Cell* lists[3] = { NULL, NULL, NULL };

/* Distribute the cells across the lists. */
while (list != NULL) {
    /* Cache a pointer to the next cell in the list, since we will be
     * rewiring this linked list.
     */
    Cell* next = list->next;

    /* Prepend this cell to the list it belongs to. */
    list->next = lists[list->value];
    lists[list->value] = list;

    /* Advance to the next cell in the list. */
    list = next;
}

希望这有帮助!

答案 1 :(得分:2)

正如其他人所说,没有反向链接就没有办法在链表中“备份”。虽然它不是您问题的答案,但是可以通过三个队列实现具有三个存储桶的存储桶排序来轻松完成排序。

队列的优势(副推送堆栈)是排序稳定。也就是说,如果列表节点中有数据(0,1,2值键除外),则每个键的顺序保持不变。

这只是许多情况中的一种,其中数组的规范算法不是列表的最佳算法。

有一种非常灵活,简单的方法来实现队列:循环链接列表,其中第一个节点,例如p,是队列的 tail ,因此{{1}是头脑。有了这个,代码简洁。

p->next

现在这是一个完整的简单测试,运行得很好。

这是未经测试的。 (如果我有时间,我会尝试测试它。)但它应该至少非常接近解决方案。

答案 2 :(得分:1)

使用双向链表。如果您已经实现了链接列表对象和相关的链接列表节点对象,并且能够在向前方向上遍历它,那么反向遍历并不是一大堆工作。

假设你有一个Node对象:

public class Node
{
    public Node Next;
    public Object Value;
}

然后您真正需要做的就是更改Node类,然后稍微插入方法以跟踪之前出现的Node:

public class Node
{
    public Node Next;
    public Node Previous;
    public Object Value;
}

public void Insert(Node currentNode, Node insertedNode)
{
    Node siblingNode = currentNode.Next;

    insertedNode.Previous = currentNode;
    insertedNode.Next = siblingNode;

    if(siblingNode!= null)
        siblingNode.previous = insertedNode;

    currentNode.next = insertedNode;
}

PS抱歉,我没有注意到包含C ++内容的编辑,所以它更多是C#

答案 3 :(得分:1)

通过改变节点而不是节点数据适用于所有情况..希望它永远不会太晚!

METHOD(To throw some light on handling corner cases):
1. Keep three dummy nodes each for 0,1,2;
2. Iterate throught the list and add nodes to respective list.
3. Make the next of zero,one,two pointers as NULL.
4. Backup this last nodes of each list.
5. Now handle 8 different possible cases to join these list and Determine the HEAD.

zero one two
0 0 0
0 0 1
0 1 0
0 1 1
1 0 0
1 0 1
1 1 0
1 1 1

在C ++中实现此功能。

Node* sortList(Node *head)
{
   struct Node dummyzero,dummyone,dummytwo;
   dummyzero.next = dummyone.next = dummytwo.next = NULL;

   struct Node *zero =&dummyzero,*one = &dummyone,*two=&dummytwo;
   Node *curr = head,*next=NULL;
   while(curr)
   {
       next = curr->next;
       if(curr->data==0)
       {
           zero->next = curr;
           zero = zero->next;
       }
       else if(curr->data==1)
       {
           one->next = curr;
           one = one->next;
       }
       else
       {
           two->next = curr;
           two = two->next;
       }
       curr = next;
   }
   zero->next = one->next = two->next =NULL;                        //Since this dummynode, No segmentation fault here.
   Node *zerolast = zero,*onelast = one,*twolast = two;
   zero = dummyzero.next;
   one = dummyone.next;
   two = dummytwo.next;

   if(zero==NULL)
   {
        if(one==NULL)
            head = two;
        else
        {
            head = one;
            onelast->next = two;
        }
   }
   else
   {
       head = zero;
       if(one==NULL)
           zerolast->next = two;
       else
       {
           zerolast->next = one;
           onelast->next = two;
       }
   }
   return head;
}

答案 4 :(得分:0)

这个想法是使用荷兰旗帜排序算法,略有修改:
根据荷兰国旗法排序0和1,
但对于2而不是在列表末尾添加它们,请将它们保存在单独的链接列表中 最后将2的列表附加到0和1的排序列表中。

Node * sort012_linked_list(Node * head) {

  if (!head || !head->next)
    return head;

  Node * head_of_2s = NULL;

  Node * prev = NULL;
  Node * curr = head;

  while (curr) {
    if (curr->data == 0) {
      if (prev == NULL || prev->data == 0) {
        prev = curr;
        curr = curr->next;
      }
      else {
        prev->next = curr->next;
        curr->next = head;
        head = curr;
        curr = prev->next;
      }
    }
    else if (curr->data == 1) {
      prev = curr;
      curr = curr->next;
    }
    else { // curr->data == 2
      if (prev == NULL) {
        head = curr->next;
        curr->next = head_of_2s;
        head_of_2s = curr;
        curr = head;
      }
      else {
        prev->next = curr->next;
        curr->next = head_of_2s;
        head_of_2s = curr;
        curr = prev->next;
      }
    }
  }

  if (prev)
    prev->next = head_of_2s;

  return head;
}