【问题标题】:C - Moving a node in a linked listC - 在链表中移动节点
【发布时间】:2016-06-28 14:52:07
【问题描述】:

我正在处理一个链接列表,我想将列表中的一个节点从一个位置移动到另一个位置,而不会弄乱。

链表的结构是:

 struct Frame
{
    char* name; // Name and path are just pointers to strings
    unsigned int duration;
    char* path;
};

typedef struct Frame frame_t;

还有:

struct Link
{
    frame_t *frame;
    struct Link *next;
};

(这就是我被要求这样做的方式,不是我的选择)

现在我需要的是一个函数,它接收链表和一个字符串(其中一个节点的名称)和一个整数,然后将具有该名称的节点移动到该位置(接收到的整数)第一个位置是 1,而不是 0(这就是所要求的)

例如: 如果列表包含节点:[pic1, pic2, pic3, pi4] 并且用户请求将“pic1”移动到位置 3,那么新列表将是: [pic2, pic3, pic1, pic4](而 pic2 将是新的负责人)

我尝试了一些版本,但它们总是只有 80% 有效(要么切断列表,要么没有移动到正确的位置)。有什么想法吗?

这是我尝试过的功能:

 void changePos(link_t** anchor_link, char* name1, int pos)
{
    link_t* currLink = *anchor_link;
    link_t* temp = NULL;
    link_t* temp2 = NULL;
    int i;

    if (strcmp(name1, currLink->frame->name) == 0 && currLink->next)
    {
        *anchor_link = (*anchor_link)->next;
        temp = currLink;
    }
    else
    {
        while (strcmp(name1, currLink->next->frame->name) != 0 && currLink->next)
        {
            currLink = currLink->next;
        }

        temp = currLink->next;
    }

    currLink = *anchor_link;

    for (i = 1; i < pos - 1; i++) // Go up until the node before the pos (meaning if pos is 4 then node 3)
    {
            currLink = currLink->next;
    }

    // Now we insert the temp node at the pos

    temp2 = currLink->next->next;
    currLink->next->next = temp;
    temp->next = temp2;
}

【问题讨论】:

  • frame_t 未定义。你忘记了某种类型的定义吗?
  • 向我们展示您的尝试以及解决方案的问题所在。
  • 您需要出示代码,以便我们帮助您修复它(MCVE (minimal reproducible example))——向我们展示您的最大努力。 struct Link 中有很多指针。目前尚不清楚frame_t 是什么,除非它不是struct Frame,除非您错过了重要的typedef 行。要移动单链表中的节点,您需要知道要移动的节点之前的节点(因此要移动的节点),并且需要知道要移动的节点之后的节点。您必须担心退化的情况,以及标识列表头部(和尾部?)的指针。
  • @SpacePotato 感谢您更新您的问题,但我们需要的是 MCVE (Minimal, Complete, and Verifiable example),请花 5 分钟时间和read that
  • 使用位置来识别列表中的节点是不寻常的。职位是什么意思?是不是在结果列表中,移动的节点必须在编号位置?识别被移动节点应该在之前或之后移动的节点会更常见(自然?)。

标签: c pointers struct linked-list nodes


【解决方案1】:

这是我想出的代码——changePos() 函数和我创建的测试代码,以确保它是正确的。

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

struct Frame
{
    char *name;
    unsigned int duration;    // Effectively unused
//  char *path;               // Actually unused
};

typedef struct Frame Frame;

struct Link
{
    Frame *frame;
    struct Link *next;
};

typedef struct Link Link;

static void print_list(Link *root);

static void changePos(Link **anchor_link, const char *name, int pos)
{
    assert(anchor_link != 0 && name != 0 && pos >= 0);
    Link *root = *anchor_link;
    Link *link = root;
    Link *prev = 0;
    int count = 0;
    while (link != 0 && strcmp(link->frame->name, name) != 0)
    {
        prev = link;
        link = link->next;
        count++;
    }
    if (link == 0)      // Name not found - no swap!
        return;
    if (count == pos)   // Already in target position - no swap
        return;
    if (count == 0)     // Moving first item; update root
    {
        assert(link == root);
        *anchor_link = root->next;
        root = *anchor_link;
    }
    else
    {
        assert(prev != 0);
        prev->next = link->next;
    }
    // link is detached; now where does it go?
    if (pos == 0)       // Move to start; update root
    {
        link->next = root;
        *anchor_link = link;
        return;
    }
    Link *node = root;
    for (int i = 0; i < pos - 1 && node->next != 0; i++)
        node = node->next;
    link->next = node->next;
    node->next = link;
}

static void print_list(Link *root)
{
    const char *pad = "";
    while (root != 0)
    {
        printf("%s[%s]", pad, root->frame->name);
        root = root->next;
        pad = "->";
    }
}

static void free_frame(Frame *frame)
{
    if (frame != 0)
    {
        free(frame->name);
        free(frame);
    }
}

static void free_link(Link *link)
{
    while (link != 0)
    {
        Link *next = link->next;
        free_frame(link->frame);
        free(link);
        link = next;
    }
}

static Frame *make_frame(const char *name, unsigned int number)
{
    Frame *frame = malloc(sizeof(*frame));
    if (frame != 0)
    {
        frame->name = strdup(name);
        frame->duration = number;
    }
    return frame;
}

static Link *make_link(const char *name, unsigned int number)
{
    Link *link = malloc(sizeof(*link));
    if (link != 0)
    {
        link->frame = make_frame(name, number);
        link->next = 0;
    }
    return link;
}

static Link *make_list(int num, Frame *frames)
{
    Link *head = 0;
    Link *tail = 0;
    for (int k = 0; k < num; k++)
    {
        Link *link = make_link(frames[k].name, frames[k].duration);
        assert(link != 0 && link->frame != 0);  // Lazy!
        if (head == 0)
            head = link;
        if (tail != 0)
            tail->next = link;
        tail = link;
    }
    return head;
}

int main(void)
{
    Frame frames[] =
    {
        { "pic0", 0 },
        { "pic1", 1 },
        { "pic2", 2 },
        { "pic3", 3 },
        { "pic4", 4 },      // Never in the list, but searched for
    };
    enum { NUM_FRAMES = sizeof(frames) / sizeof(frames[0]) };

    for (int i = 0; i < NUM_FRAMES; i++)
    {
        for (int j = 0; j < NUM_FRAMES; j++)
        {
            Link *head = make_list(NUM_FRAMES - 1, frames);
            print_list(head);
            printf(" == %s to %u == ", frames[i].name, j);
            changePos(&head, frames[i].name, j);
            print_list(head);
            putchar('\n');
            free_link(head);
        }
    }

    return 0;
}

我怀疑changePos() 中的代码仍然可以简化一点,但我还没有发现如何。这是一个带有数字位置和列表的丑陋界面——使用另一个名称来标识节点应该移动到的位置会更自然。对于数字位置,您很想使用数组而不是列表。

示例输出:

[pic0]->[pic1]->[pic2]->[pic3] == pic0 to 0 == [pic0]->[pic1]->[pic2]->[pic3]
[pic0]->[pic1]->[pic2]->[pic3] == pic0 to 1 == [pic1]->[pic0]->[pic2]->[pic3]
[pic0]->[pic1]->[pic2]->[pic3] == pic0 to 2 == [pic1]->[pic2]->[pic0]->[pic3]
[pic0]->[pic1]->[pic2]->[pic3] == pic0 to 3 == [pic1]->[pic2]->[pic3]->[pic0]
[pic0]->[pic1]->[pic2]->[pic3] == pic0 to 4 == [pic1]->[pic2]->[pic3]->[pic0]
[pic0]->[pic1]->[pic2]->[pic3] == pic1 to 0 == [pic1]->[pic0]->[pic2]->[pic3]
[pic0]->[pic1]->[pic2]->[pic3] == pic1 to 1 == [pic0]->[pic1]->[pic2]->[pic3]
[pic0]->[pic1]->[pic2]->[pic3] == pic1 to 2 == [pic0]->[pic2]->[pic1]->[pic3]
[pic0]->[pic1]->[pic2]->[pic3] == pic1 to 3 == [pic0]->[pic2]->[pic3]->[pic1]
[pic0]->[pic1]->[pic2]->[pic3] == pic1 to 4 == [pic0]->[pic2]->[pic3]->[pic1]
[pic0]->[pic1]->[pic2]->[pic3] == pic2 to 0 == [pic2]->[pic0]->[pic1]->[pic3]
[pic0]->[pic1]->[pic2]->[pic3] == pic2 to 1 == [pic0]->[pic2]->[pic1]->[pic3]
[pic0]->[pic1]->[pic2]->[pic3] == pic2 to 2 == [pic0]->[pic1]->[pic2]->[pic3]
[pic0]->[pic1]->[pic2]->[pic3] == pic2 to 3 == [pic0]->[pic1]->[pic3]->[pic2]
[pic0]->[pic1]->[pic2]->[pic3] == pic2 to 4 == [pic0]->[pic1]->[pic3]->[pic2]
[pic0]->[pic1]->[pic2]->[pic3] == pic3 to 0 == [pic3]->[pic0]->[pic1]->[pic2]
[pic0]->[pic1]->[pic2]->[pic3] == pic3 to 1 == [pic0]->[pic3]->[pic1]->[pic2]
[pic0]->[pic1]->[pic2]->[pic3] == pic3 to 2 == [pic0]->[pic1]->[pic3]->[pic2]
[pic0]->[pic1]->[pic2]->[pic3] == pic3 to 3 == [pic0]->[pic1]->[pic2]->[pic3]
[pic0]->[pic1]->[pic2]->[pic3] == pic3 to 4 == [pic0]->[pic1]->[pic2]->[pic3]
[pic0]->[pic1]->[pic2]->[pic3] == pic4 to 0 == [pic0]->[pic1]->[pic2]->[pic3]
[pic0]->[pic1]->[pic2]->[pic3] == pic4 to 1 == [pic0]->[pic1]->[pic2]->[pic3]
[pic0]->[pic1]->[pic2]->[pic3] == pic4 to 2 == [pic0]->[pic1]->[pic2]->[pic3]
[pic0]->[pic1]->[pic2]->[pic3] == pic4 to 3 == [pic0]->[pic1]->[pic2]->[pic3]
[pic0]->[pic1]->[pic2]->[pic3] == pic4 to 4 == [pic0]->[pic1]->[pic2]->[pic3]

输出的 LHS 是之前的列表 — 始终相同,顺序为 pic0 到 pic3。输出的 RHS 是调用changePos 后的列表。对于位置 n = 0..3,您可以看到,'picn' 依次从位置 0 迁移到位置 3。名称pic4 从未插入到列表中,因此在查找时不会发生任何变化。此外,当您尝试将任何名称移动到不存在的位置时,它会移动到列表中的最后一个位置。小于 0 的位置被断言为无效。

代码有偶然的错误检查。它断言解决了内存分配问题以及其他一些问题(例如位置小于 0)。

changePos() 中写入任何有价值的内容之前,我使用changePos() 的虚拟无操作版本使安全带干净利落地运行。

使用我习惯的编译器警告选项,代码在 Mac OS X 10.11.5 和 GCC 6.1.0 和 Valgrind 3.12.0.SVN 上编译和运行干净:

$ gcc -O3 -g -std=c11 -Wall -Wextra -Wmissing-prototypes -Wstrict-prototypes \
>     -Wold-style-definition -Werror so.3807-9550.c -o so.3807-9550
$

【讨论】:

  • 您的版本完美运行,但有两点。 1. 我不知道断言是什么,但似乎用起来很舒服。 2. 您使用索引 0,如果索引 4 是最后一个,则索引 3 和 4 的结果相同。我把它改成了我需要的(因为 1 是第一个索引/位置,如果有 4 个节点,那么 4 是最后一个),效果很好。谢谢!
【解决方案2】:

我认为您的一般方法很好:找到目标节点,将其从列表中删除,然后将其重新插入指定位置。但是,您的代码有几个问题。

首先,考虑这个while()循环的条件:

    while (strcmp(name1, currLink->next->frame->name) != 0 && currLink->next)

如果您不确定currLink-&gt;next 是否为NULL(并且您应该是),那么您必须在取消引用之前执行空值检查,您可以在表达式currLink-&gt;next-&gt;frame-&gt;name 中执行此操作。实际上,如果您的代码在未找到指定名称的情况下到达列表末尾,则会表现出未定义的行为。

其次,您的变量命名还有一些不足之处。 temp 是什么? temp2 是什么?鉴于看不到name2name1 中的1 表示什么?以明确传达其用途的方式命名变量有助于编写和分析代码。例如,“target_node”有什么问题?

三、你的插入代码错误:

temp2 = currLink->next->next;
currLink->next->next = temp;
temp->next = temp2;

你打算在*currLink*currLink-&gt;next之间插入*temp,但实际上你把它放在了*currLink-&gt;next之后,然后关闭了一个二元素循环。另外,我怀疑使用temp2 只是混淆了这段代码。我会这样写:

temp->next = currLink->next->next;
currLink->next = temp;

第四,您无法在列表中的第 1 位插入。你这样做的方式需要一个特殊情况。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-01-25
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-07-04
    • 1970-01-01
    相关资源
    最近更新 更多