跟踪链表

时间:2015-05-23 00:54:22

标签: c linked-list

通过链接列表迭代似乎对我来说有点棘手(正如我正在学习的那样)。

我一直在递归地做事,但我想迭代地做这件事。

如果其值大于l,则下一个函数会将列表mx中的值添加到列表x中:

typedef struct slist *Lint;

typedef struct slist {
    int value;
    Lint next;
} Nodo;

void split(Lint l, int x, Lint *mx){
    Lint mxtmp=NULL;

    while (l!=NULL){
        if(l->value > x){
            *mx = (Lint) calloc(1,sizeof(Nodo));
            (*mx)->value = l->value;
            (*mx)->next = NULL;
            mxtmp = *mx;
            *mx = (*mx)->next;
        }

        l = l->next;
    }

    *mx = mxtmp;

}

打印*mx而不是列表会给我一个值(使用适当的循环来浏览列表)。

事情是,似乎我忘记了列表的开头;在这种情况下,我似乎无法跟踪指向列表*mx开头的指针,这是我要更改的列表。

我该怎么办?

我对此函数的另一个疑问是,我怎么能不将值归因于新列表,而是创建新的mx列表,只需指向主列表l中的结构?

2 个答案:

答案 0 :(得分:1)

typedef struct slist *Lint;

typedef struct slist {
    int value;
    Lint next;
    } Nodo;

void split(Lint l, int x, Lint *mx){
     Lint mxtmp=NULL;
     int i = 0;
     while (l!=NULL){
         if(l->value > x){
             *mx = (Lint) calloc(1,sizeof(Nodo));
             (*mx)->value = l->value;
             (*mx)->next = NULL;
             if(!i) //mxtmp will keep track of the first allocated Nodo item
             {
               mxtmp = *mx;
               i++;
             }
             mx = &((*mx)->next); // with this, next( so *mx) will be initialized to point to a new Nodo item in the next loop
         }

         l = l->next;
     }

     mx = &mxtmp; //finally we make mx point to the first allocated Nodo item
}

我做了以下代码来测试它:

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

typedef struct slist *Lint;

typedef struct slist {
    int value;
    Lint next;
    } Nodo;

void recurs_free(Lint f)
{
   if(!f)return;
   if(f->next)recurs_free(f->next);
   free(f);
}

void recurs_printf(Lint f)
{
   if(!f)return;
   printf("%d\n", f->value);
   if(f->next)recurs_printf(f->next);
}

void split(Lint l, int x, Lint *mx){
     Lint mxtmp=NULL;
     int i = 0;
     while (l!=NULL){
         if(l->value > x){
             *mx = (Lint) calloc(1,sizeof(Nodo));
             (*mx)->value = l->value;
             (*mx)->next = NULL;
             if(!i) //mxtmp will keep track of the first allocated Nodo item
             {
               mxtmp = *mx;
               i++;
             }
             mx = &((*mx)->next); // with this, next( so *mx) will be initialized to point to a new Nodo item in the next loop
         }

         l = l->next;
     }
     mx = &mxtmp; //finally we make mx point to the first allocated Nodo item
}

int main()
{
    void recurs_printf(Lint f);
    void recurs_free(Lint f);
    void split(Lint l, int x, Lint *mx);
    Lint t = NULL;
    Nodo a = {1, NULL}, b = {2, &a}, c = {3, &b}, d = {4, &c}, e = {5, &d};
    split(&e, 3, &t);
    recurs_printf(t);
    recurs_free(t);
    return 0;
}

答案 1 :(得分:1)

诊断

您的问题出在以下代码中:

void split(Lint l, int x, Lint *mx){
    Lint mxtmp=NULL;

    while (l!=NULL){
        if(l->value > x){
            *mx = (Lint) calloc(1,sizeof(Nodo));

您对*mx的作业意味着您丢失了原始指针,无法正确更新您的列表。很少有平台使用calloc()不能将指针设置为null,但是当代码显式设置结构内容时,使用calloc()毫无意义。

您可能需要更多类似的内容:

void split(Lint l, int x, Lint *mx)
{
    Lint mxtmp = NULL;

    while (l != NULL)
    {
        if (l->value > x)
        {
            mxtmp = (Lint) malloc(sizeof(*mxtmp));
            if (mxtmp == 0)
                …report error and do not continue…
            mxtmp->value = l->value;
            mxtmp->next = *mx;
            *mx = mxtmp;
        }
        l = l->next;
    }
}

这将*mx上的项目按照它们在l中出现的顺序插入,因为它始终插入列表的前面。如果您愿意,可以安排附加到*mx的末尾。

如果您需要从源列表l中删除条目,并将其转移到*mx,则需要将Lint *l传递给该函数,以便列表的头部如果l中的第一项(或带有修订签名的*l)需要转移到*mx,则可以更新。

解释

  

这很好地完成了这项工作。你介意解释*mx会发生什么吗?因为我似乎不明白*mx是如何增加的#34 ;;似乎*mx始终处于相同的指针值。

我感到宽慰,因为我没有机会在此次更新之前对其进行测试。

我们假设我们接到这样的电话:

Lint list1 = 0;
…populate list1…
Lint list2 = 0;
split(list1, 10, &list2);

指针list2有自己的地址。它包含一个值,最初是空指针。 list2指针的地址传递给split(),这意味着split()可以修改指针内容(就像你有int i = 0; some_func(&i);一样,然后是some_func()可以修改i)中的值。

split()遍历列表l时,当它找到需要复制的值时,它会创建mxtmp指向的新节点,并填入该值。它使新节点的next指针指向当前位于列表头部的内容(mxtmp->next = *mx;),然后它修改*mx - 有效list1的地址{1}}在示例调用中的调用函数中 - 指向它包含新节点的地址(*mx = mxtmp;)。

测试

这是我提出的测试代码,它在valgrind下完全运行:

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

typedef struct slist *Lint;

typedef struct slist
{
    int value;
    Lint next;
} Nodo;

static void error(const char *msg)
{
    fprintf(stderr, "%s\n", msg);
    exit(EXIT_FAILURE);
}

static void split(Lint l, int x, Lint *mx)
{
    // Note minor change compared to original suggested code.
    while (l != NULL)
    {
        if (l->value > x)
        {
            Lint mxtmp = (Lint) malloc(sizeof(*mxtmp));
            if (mxtmp == 0)
                error("Out of memory");
            mxtmp->value = l->value;
            mxtmp->next = *mx;
            *mx = mxtmp;
        }
        l = l->next;
    }
}

static void print_list(const char *tag, Lint list)
{
    printf("%s:", tag);
    while (list != NULL)
    {
        printf(" --> %d", list->value);
        list = list->next;
    }
    putchar('\n');
}

static void insert(Lint *list, int value)
{
    Lint node = malloc(sizeof(*node));
    if (node == 0)
        error("Out of memory");
    node->value = value;
    node->next = *list;
    *list = node;
}

static void destroy(Lint list)
{
    while (list != NULL)
    {
        Lint next = list->next;
        free(list);
        list = next;
    }
}

int main(void)
{
    Lint list1 = NULL;
    insert(&list1, 2);
    insert(&list1, 10);
    insert(&list1, 11);
    insert(&list1, 23);
    print_list("List 1", list1);

    Lint list2 = NULL;
    split(list1, 10, &list2);
    print_list("List 1", list1);
    print_list("List 2", list2);

    destroy(list1);
    destroy(list2);
    return 0;
}

汇编

源文件名为nodo.c,因此程序名为nodo,我配置了makefile,并启用了许多编译器警告选项。没有警告(来自Mac OS X 10.10.3上的GCC 5.1.0):

$ gcc -O3 -g -std=c11 -Wall -Wextra -Werror nodo.c -o nodo
$

我认为这是最低要求;我通常甚至不会运行代码,直到它安静地编译。

示例输出:

List 1: --> 23 --> 11 --> 10 --> 2
List 1: --> 23 --> 11 --> 10 --> 2
List 2: --> 11 --> 23

打印list1两次是偏执狂,检查它是否在split操作期间没有损坏。请注意,应编写split()函数以使用insert()函数。

调试打印还会打印节点的地址和下一个指针:

static void print_list(const char *tag, Lint list)
{
    printf("%s:\n", tag);
    while (list != NULL)
    {
        printf("--> %3d  (%p, next %p)\n", list->value, (void *)list, (void *)list->next);
        list = list->next;
    }
    putchar('\n');
}

屈服,例如:

List 1:
-->  23  (0x10081f410, next 0x10081f3c0)
-->  11  (0x10081f3c0, next 0x10081f370)
-->  10  (0x10081f370, next 0x10081f320)
-->   2  (0x10081f320, next 0x0)

List 1:
-->  23  (0x10081f410, next 0x10081f3c0)
-->  11  (0x10081f3c0, next 0x10081f370)
-->  10  (0x10081f370, next 0x10081f320)
-->   2  (0x10081f320, next 0x0)

List 2:
-->  11  (0x10081f4b0, next 0x10081f460)
-->  23  (0x10081f460, next 0x0)

分手评论

我没有重新组织数据类型,但留给我自己的设备我可能会使用类似的东西:

typedef struct List List;
struct List
{
    int   value;
    List *next;
};

然后通过List *List **。这使用标记名称作为类型名称,这是C ++在没有显式typedef的情况下自动执行的操作。