【问题标题】:How to split doubly linked list alternately如何交替拆分双向链表
【发布时间】:2014-01-27 14:37:26
【问题描述】:

好的。我有一个 c 编程课程的作业。 我需要实现一个函数原型:

void split(node* head, node **first, node **second)   

该函数将head指向的双向链表拆分为两个列表firstsecond

假设head 包含元素 F0,S0,F1,S1,F2,S2,...

然后:

  • first 应按以下顺序包含元素:F0,F1,F2,...
  • second 应按以下顺序包含元素:S0,S1,S2,...

不要进行任何分配或释放(malloc、calloc、realloc、free)。只更新指针。不要更改节点数据。

限制:不要使用 malloc()、calloc()、realloc()、free()。

我卡住了,我无法生成任何算法。请帮忙!

typedef struct node
{
    int data;
    struct node *prev;
    struct node *next;
} node;

编辑解决方案:

    #define DATA(p) ((p)->data)
    #define NEXT(p) ((p)->next)
    #define PREV(p) ((p)->prev)



    void split ( node* head, node **first, node **second )
    {
        node* firstCurrent = head;
        node* secondCurrent = NULL;
        node* dummyforbprev = NULL;

        if ( firstCurrent )
        {
            secondCurrent = NEXT(firstCurrent);
            if(secondCurrent)
                PREV(secondCurrent)=NULL;
        }

        *first = firstCurrent;
        *second = secondCurrent;

        while ( firstCurrent && secondCurrent )
        {
            NEXT(firstCurrent) = NEXT(secondCurrent);
            dummyforbprev = PREV(firstCurrent);
            firstCurrent = NEXT(firstCurrent);
            if(firstCurrent)
                PREV(firstCurrent) = PREV(secondCurrent);

            if ( firstCurrent )
                NEXT(secondCurrent) = NEXT(firstCurrent);
            PREV(secondCurrent) = dummyforbprev;
            secondCurrent = NEXT(secondCurrent);
        }

        if ( firstCurrent )
            NEXT(firstCurrent) = NULL;

        if ( secondCurrent )
            NEXT(secondCurrent) = NULL;
    }

【问题讨论】:

  • 显示您当前拥有的代码。
  • 将 i 设置为 0。对于 head 中的每个节点:从 head 中删除节点。如果 i 是偶数,则将节点附加到 first 的尾部,如果奇数,则将节点附加到 second 的尾部。递增 i 并循环。
  • @Magnus Reftel 如何删除和附加节点?你能解释一下吗?
  • @M Oehm 实际的问题是如何在不进行任何分配或释放的情况下做到这一点。不,这不一定。我已经实现了 delete_node() 和 insert_node() 函数。
  • 你已经有了节点,所以你不需要克隆它来获得一个新的副本或任何东西。只需更改其nextprev 指针指向的内容(更新旧列表中的指针以匹配)。 en.wikipedia.org/wiki/Doubly_linked_list 有一个相当不错的解释

标签: c doubly-linked-list


【解决方案1】:

无需为新列表分配任何新节点,只需调整 指针 即可形成新列表,请记住,这必然会修改您传入的列表。

从如下列表开始:

head -> 1 -> 2 -> 3 -> 4 -> 6 -> null

以两个节点为一组工作并将它们分配到新列表中相对容易。您基本上首先要确保列表中有两个或更多节点,否则返回的第一个列表是原始列表,而第二个列表为空。

检查完后,设置要返回的列表的头指针,以及跟踪每个列表中最后一项的漫游指针:

second ------|
first --|    |
head -> 1 -> 2 -> 3 -> 4 -> 6 -> null
fEnd ---|    |
sEnd --------|

然后,简单地说,您执行以下步骤:

  • fEnd 下一个值设置为sEnd 下一个值(所以1 现在指向3)然后将fEnd 指针前进到3
  • sEnd 下一个值设置为fEnd 下一个值(所以2 现在指向4)然后将sEnd 指针前进到4

现在你有这种情况:

sEnd --------------------|
fEnd ---------------|    |
           ________ |    |
          /        \     |
first/ -> 1    2    3 -> 4 -> 6 -> null
 head          |\        /
               | \______/
second --------|

您可以看到列表的前面部分已被拆分,并且指针已前进到列表的其余部分。

您只需继续执行此操作,直到到达列表末尾。

现在您会注意到上面提到的“简单地”这个词。虽然这里解释的概念很简单,但也有一些复杂的因素。

第一个事实是您可能能够处理两个节点的组,这仅仅是因为列表中可能有奇数个节点。其次,在处理链表的末端时通常会遇到一些问题,您必须小心处理以下问题:

node->next->prev = node;

node 可能位于末尾,当您取消引用 node->next 时可能会导致崩溃。

不过,这只是函数内置的一点额外安全性。你可以在下面看到一个完整的程序,它说明了如何做到这一点,首先是标题、节点结构和一个辅助函数,用于以可读形式转储列表(并确保它没有损坏):

#include <stdio.h>
#include <stdlib.h>

typedef struct sNode {
    int value;
    struct sNode *next;
    struct sNode *prev;
} tNode;

static void dump (char *desc, tNode *head) {
    printf ("Dumping %s: ", desc);
    tNode *p = head;
    while (p != NULL) {
        if ((p != head) && (p->prev->next != p)) {
            printf ("double-link error\n");
            exit (1);
        }
        printf ("%d -> ", p->value);
        p = p->next;
    }
    puts ("NULL");
}

其次,解决方案的“肉”,根据您的要求使用split() 函数,它采用单个列表并返回两个列表,每个列表具有原始值的交替值:

void split (tNode* head, tNode **pFirst, tNode **pSecond) {
    // Must have two elements or more to split.

    if ((head == NULL) || (head->next == NULL)) {
        *pFirst = head;
        *pSecond = NULL;
        return;
    }

    // Set up list heads and roving pointers.

    tNode *first = head, *second = head->next;
    tNode *fEnd = first, *sEnd = second;

    // Do whole list in groups of two, but check to ensure
    //   no crashes due to invalid pointer dereferences.

    while (fEnd != NULL) {
        // First in group of two.

        if (sEnd != NULL) {
            fEnd->next = sEnd->next;
            if (fEnd->next != NULL)
                fEnd->next->prev = fEnd;
        }
        if (fEnd != NULL)
            fEnd = fEnd->next;

        // Second in group of two.

        if (fEnd != NULL) {
            sEnd->next = fEnd->next;
            if (sEnd->next != NULL)
                sEnd->next->prev = sEnd;
        }
        if (sEnd != NULL)
            sEnd = sEnd->next;
    }

    // Return lists to caller.

    *pFirst = first;
    *pSecond = second;
}

如前所述,该函数也会影响原始列表,因为它必须使用原始节点,并且更改这些节点中的 next/prev 值也会使原始列表变形。

最后一段代码是测试工具,它只是为您提供每个节点中递增整数的列表(大小取决于您提供的任何参数,如果您不提供,则为十个)。

它构造并输出列表,然后调用split() 函数并显示结果:

int main (int argc, char *argv[]) {
    int quant = (argc > 1) ? atoi (argv[1]) : 10;
    tNode *head = NULL, *first, *second;

    for (int i = quant - 1; i >= 0; i--) {
        tNode *add = malloc (sizeof (*add));
        add->value = i + 1;
        add->next = head;
        add->prev = NULL;
        if (head != NULL) head->prev = add;
        head = add;
    }

    dump ("before", head);
    split (head, &first, &second);
    dump ("after (first) ", first);
    dump ("after (second)", second);

    return 0;
}

该程序的输出可以在以下脚本中看到:

$ ./testprog 0
Dumping before: NULL
Dumping after (first) : NULL
Dumping after (second): NULL

$ ./testprog 1
Dumping before: 1 -> NULL
Dumping after (first) : 1 -> NULL
Dumping after (second): NULL

$ ./testprog 2
Dumping before: 1 -> 2 -> NULL
Dumping after (first) : 1 -> NULL
Dumping after (second): 2 -> NULL

$ ./testprog 3
Dumping before: 1 -> 2 -> 3 -> NULL
Dumping after (first) : 1 -> 3 -> NULL
Dumping after (second): 2 -> NULL

$ ./testprog
Dumping before: 1 -> 2 -> 3 -> 4 -> 5 -> 6 -> 7 -> 8 -> 9 -> 10 -> NULL
Dumping after (first) : 1 -> 3 -> 5 -> 7 -> 9 -> NULL
Dumping after (second): 2 -> 4 -> 6 -> 8 -> 10 -> NULL

$ ./testprog 9
Dumping before: 1 -> 2 -> 3 -> 4 -> 5 -> 6 -> 7 -> 8 -> 9 -> NULL
Dumping after (first) : 1 -> 3 -> 5 -> 7 -> 9 -> NULL
Dumping after (second): 2 -> 4 -> 6 -> 8 -> NULL

【讨论】:

    【解决方案2】:

    我们也可以使用队列操作和bool或int变量来
    在目的地列表之间切换

    altsplit(L:List;var L1,L2:List)
       L1.head = NIL;
       L1.tail = NIL;
       L2.head = NIL;
       L2.tail = NIL;
       s = false
       while not isEmpty(L) do
            x = dequeue(L)
            if not s then
                 enqueue(L1,x)
            else
                 enqueue(L2,x);
            s := not s;
    

    请注意,我们不需要为新节点分配内存
    我们只需像在队列中一样设置指针

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2015-12-11
      • 1970-01-01
      • 2017-04-07
      • 2022-01-15
      • 2012-05-10
      • 1970-01-01
      • 2015-10-12
      相关资源
      最近更新 更多