为什么这只会返回我单链表中的一个节点?

时间:2012-10-06 11:21:53

标签: c

我有一个结构,

    typedef struct song{
    string title;
    string artist;
    string album;
    string genre;
    float rating;
    struct song *next;
    }song_t;

和删除功能,

    song_t *DeleteSong(song_t *head, string key){
    song_t *temp, *temp2, *holder;
    holder = (song_t *)malloc(sizeof(song_t));  
    temp = head;


    while(temp != NULL && strcasecmp(temp->title, key) != 0){
        temp = temp->next;
    }
    if(temp == NULL){
        newline;
        printf("Song not found.");
        newline;
        newline;
    }

    else if(temp == head){
        if(temp->next == NULL){
            temp->next = NULL;
            free(temp);
            return NULL;
        }
        else{
        head = temp->next;
        }
    }

    else if(temp->next == NULL){
        temp2 = head;

        while(temp2->next->next != NULL){
            temp2 = temp2->next;
        }
        holder = temp2->next->next;
        temp2->next = NULL;
    }

    else{
        temp2 = head;
        while(temp2->next != temp){
            temp2 = temp2->next;
        }
        holder = temp;
        temp2->next = temp->next;
        free(holder);
    }
    return head;
    }

最后,删除重复的函数,

song_t *RemoveDuplicate(song_t *head){
    song_t *temp;
    song_t *temp2;
    temp = head;
    temp2 = temp->next;

    while(temp != NULL){
        while(temp2 != NULL){
            if(strcasecmp(temp->title,temp2->title) == 0 && strcasecmp(temp->artist,temp2->artist) == 0 && strcasecmp(temp->album,temp2->album) == 0 && strcasecmp(temp->genre,temp2->genre) == 0){
                if(temp2 == temp->next){
                    temp = DeleteSong(temp,temp2->title);
                }
                else{
                    temp2 = DeleteSong(temp2->next,temp2->title);
                }
            }
            temp2 = temp2->next;
        }
        temp = temp->next;
    }

    }

但是,每当我在main中包含删除重复功能时,     head = RemoveDuplicate(head);

结果总是只返回一个结构并删除整个列表。我认为RemoveDuplicate函数有问题,因为我测试了DeleteSong函数,它运行良好。

2 个答案:

答案 0 :(得分:0)

如果列表中的前两个条目是重复的,那么它似乎是您第一次检查:

if(temp2 == temp->next){
    temp = DeleteSong(temp,temp2->title);
}

它将在列表的头部评估为真。如果您的列表突然缩小到列表的头部,您可能想要查看从那里发生的事情。

我怀疑这个错误实际上可能在DeleteSong函数中找到,因为它看起来比我从其他单链表中看到的要复杂得多。

在伪C中,我可能会尝试这样的事情:

to_delete = find_node_by_key( key )
return if to_delete == null

current = head
last    = null

if to_delete == current
{
    head = current->next
    to_mem_free = current
}
else do
{
    if to_delete == current
    {
        to_mem_free = current
        if ( last != null ) last->next = current->next
        break
    }

    last = current

} while (current = current->next) != null

您可以将要删除的节点的搜索与实际删除组合在一起,这样您就不必两次遍历列表。您可能还想尝试避免使用“temp”作为变量名称,除非绝对必要,因为它经常会引起混淆。

许多工具链包括一个提供单链表的库。您可以使用这样的库来节省数小时的编码和调试,这可能还提供排序的树或哈希列表,可以显着加快搜索,例如歌曲标题。

答案 1 :(得分:0)

holder = (song_t *)malloc(sizeof(song_t));
/* snip */
/* else if */
    holder = temp2->next->next;
/* else */
    holder = temp;
    temp2->next = temp->next;
    free(holder);

当您指定holder = temp2->next->next;(顺便说一下,那里总是NULL)或holder = temp;时,您将丢失对malloc内存的引用。由于您根本没有真正使用holder,因此修复方法是将其从函数中删除。在第一种情况下,您没有以任何方式使用它,除了分配复杂的NULL,在第二种情况下,删除holder = temp;free temp是正确的方法。

还有一些更奇怪的事情和错误:

song_t *DeleteSong(song_t *head, string key){
    song_t *temp, *temp2, *holder;
    holder = (song_t *)malloc(sizeof(song_t));  // as said, remove holder completely
    temp = head;

    /* Find song with given title */
    while(temp != NULL && strcasecmp(temp->title, key) != 0){
        temp = temp->next;
    }
    if(temp == NULL){
        newline;
        printf("Song not found.");
        newline;
        newline;
    }
    /* It's the very first in the list */
    else if(temp == head){
        if(temp->next == NULL){
            /* It's even the only one */
            temp->next = NULL;    // This runs only if temp->next is already NULL
            free(temp);           // Also free the members of temp, or you're leaking
            return NULL;
        }
        else{
            head = temp->next;    // You should now free temp and members, or you're leaking memory
        }
    }
    /* It's the last one in the list, but not the first */
    else if(temp->next == NULL){
        temp2 = head;
        /* Find the penultimate song */
        while(temp2->next->next != NULL){
            temp2 = temp2->next;
        }
        holder = temp2->next->next;
        temp2->next = NULL;    // You should now free temp and members, or you're leaking memory
    }
    /* Neither first nor last */
    else{
        temp2 = head;
        /* Find song before */
        while(temp2->next != temp){
            temp2 = temp2->next;
        }
        holder = temp;
        temp2->next = temp->next;
        free(holder);
    }
    return head;
}

但除了泄漏之外,它是正确的,虽然不必要地复杂。

song_t *DeleteSong(song_t *head, string key) {
    song_t *prev = NULL, curr = head;
    /* Find song and node before that */
    while(curr != NULL && strcasecmp(curr->title, key) != 0) {
        prev = curr;
        curr = curr->next;
    }
    if (curr == NULL) {
        /* Not found */
        newline;
        printf("Song not found.");
        newline;
        newline;
    } else if (prev == NULL) {
        /* It's the very first song in the list
         * so let head point to its successor
         * and free the song; it doesn't matter
         * if it's the last in the list
         */
        head = curr->next;
        free(curr->title);   // Probably, but not if title isn't malloced
        free(curr->artist);  // Ditto
        free(curr->album);
        free(curr->genre);
        free(curr);
    } else {
        /* We have a predecessor, let that point
         * to the successor and free the song
         */
        prev->next = curr->next;
        free(curr->title); // See above
        free(curr->artist);
        free(curr->album);
        free(curr->genre);
        free(curr);
    }
    return head;
}

另一方面,RemoveDuplicate功能不符合您的要求。除了重复紧跟原始副本和不存在副本的情况之间的区别,我认为没有理由,分配temp = DeleteSong(temp,temp2->title);temp2 = DeleteSong(temp2->next,temp2->title);更改了temp的内容。 temp2指向,但不是列表中各自的前任指向的位置。让我们用一点点ASCII艺术来说明问题:

       temp    temp2
         |      |
         v      v
song1->song2->song3->song4->song5->...

其中song2song3是重复的。现在位于temp = DeleteSong(temp,temp2->title);,因为这两首歌是重复的,head的{​​{1}}参数已经匹配,而在DeleteSong的版本中,DeleteSong就是所有已完成,所以列表根本没有改变,你只需要

head = temp->next; return head;

两个指针指向同一个列表节点。在我的 temp temp2 | | v v song1->song2->song3->song4->... 版本中释放节点,DeleteSong现在将指向song1.next d内存,哎哟。

如果副本没有紧跟原作,free可能找不到匹配标题的歌曲,因为搜索在已知匹配后开始,在这种情况下列表是根本没有修改,temp2 = DeleteSong(temp2->next,temp2->title);只是改为指向继任者。如果之后存在匹配标题的歌曲,则仍未从列表中删除找到的副本,并且之后的部分可能会更改,可能导致不健全的状态。如果temp2指向列表中的倒数第二个节点,并且最后一个节点具有匹配的标题,则最后一个节点为temp2 d,但重复内容的free指针不是' t改变了,所以现在它指向next d内存,你有一个悬空指针(这是一个等待发生的段错误。)

另一个问题是,freeDeleteSong中的删除标准不同,前者只检查标题,后者也是艺术家,专辑和流派,所以使用后者的前一个功能有可能删除不应删除的歌曲(考虑封面版本)。

如果要从单个链接列表中删除节点,则需要指向前一个节点的指针才能更改指向的内容,或者您​​将创建悬空指针。我已经给出了一种标准的方法,你可以基本上将它复制到内部循环中,但是在这里我们永远不会想要删除第一个节点,所以它有点简单:

RemoveDuplicate