【问题标题】:I have a linked list, I would like to remove duplicate values我有一个链表,我想删除重复值
【发布时间】:2020-03-16 03:34:20
【问题描述】:

所以,我创建了一个代码,它创建了一个包含 5 个值的链表。我想知道删除这些值的重复项并再次打印没有重复项的链接列表的最佳方法是什么。

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

/* self-referential structure*/
struct studentID{
    int value;          //a data member which is an integer
    struct studentID *next;         //a data member which is a pointer to next node
};


typedef struct studentID STUDENTID;     //creating a nickname for struct studentID as STUDENTID
typedef STUDENTID *STUDENTIDPtr;        //creating a nickname for STUDENTID as STUDENTIDPtr


//Global variables
STUDENTIDPtr previousPtr;           //pointer to previous node in list
STUDENTIDPtr currentPtr;            //pointer to current node in list


void printList(STUDENTIDPtr currentPtr){


    while (currentPtr != NULL){         //while not the end of the list
        printf("%d -> ", currentPtr->value);
        currentPtr = currentPtr ->next;
    }
}


int main(){
    STUDENTIDPtr newPtr1;           //creating a pointer to create a new node
    STUDENTIDPtr newPtr2;           //creating a pointer to create a new node
    STUDENTIDPtr newPtr3;           //creating a pointer to create a new node
    STUDENTIDPtr newPtr4;           //creating a pointer to create a new node
    STUDENTIDPtr newPtr5;           //creating a pointer to create a new node


    //creation of the first node
    newPtr1 = malloc(sizeof(STUDENTID));            //This is when a node is created
    newPtr2 = malloc(sizeof(STUDENTID));            //This is when a node is created
    newPtr3 = malloc(sizeof(STUDENTID));            //This is when a node is created
    newPtr4 = malloc(sizeof(STUDENTID));            //This is when a node is created
    newPtr5 = malloc(sizeof(STUDENTID));            //This is when a node is created


    newPtr1 -> value = 4; // assign data in first node 
    newPtr1 -> next = newPtr2;


    newPtr2 -> value = 4; // assign data in first node 
    newPtr2 -> next = newPtr3;


    newPtr3 -> value = 5; // assign data in first node 
    newPtr3 -> next = newPtr4;


    newPtr4 -> value = 2; // assign data in first node 
    newPtr4 -> next = newPtr5;


    newPtr5 -> value = 1; // assign data in first node 
    newPtr5 -> next = NULL;


    currentPtr = newPtr1;

    printList(newPtr1);


    return 0;
}

使用 if else 并遍历每个链表会很容易还是有更好的方法?

【问题讨论】:

  • 不清楚你是指相邻的重复元素还是所有重复的元素。

标签: c algorithm struct linked-list singly-linked-list


【解决方案1】:

有两种方法立即浮现在脑海中,使用哪一种取决于您的具体情况,以及您是否希望保留元素的原始顺序。


第一种方法:

使用双循环并一次拾取一个节点。然后在该节点之后迭代列表,如果找到重复项,请删除。重复挑选节点,直到您遍历整个列表。

For every node of the list
  For every next_node after node
    If next_node.value == node.value
      Remove that next_node

这种方法保留了元素的原始顺序。

我认为这种方法是您已经想到的。我建议你从那个开始。

例子:

1 -> 2 -> 3 -> 4 -> 1

我将从第一个节点(1)开始,检查第二个节点,第三个,第四个,到目前为止什么都没有,没有发现重复。我现在检查第五个节点,它的值也为 1(发现重复!),所以我将其删除。

现在列表如下所示:

1 -> 2 -> 3 -> 4

现在我正在寻找第二个节点的重复项(我在之前的遍历中检查了第一个节点)。我检查了 3,我检查了 4,没有发现重复项。列表保持不变。

现在我正在寻找第三个节点的重复项。我检查了 4,没有发现重复项。列表保持不变。

现在我正在寻找第四个节点的重复项。 next节点为NULL,表示第四个节点是最后一个节点(因为我们在第一次遍历中去掉了第五个节点,作为1的重复)。没有什么要检查的,列表保持不变:

1 -> 2 -> 3 -> 4

观察对于我想要检查是否存在重复的每个节点,我如何遍历列表直到其结束。因此,对于每个节点,我都在进行 O(N) 遍历,其中 N 是列表的大小。

我有多少个节点?否

所以这种方法的时间复杂度是 N * O(N) = O(N2)

我强烈建议您自己尝试并练习。完成后,您可以阅读Remove duplicates from an unsorted list 来检查您的解决方案。


第二种方法:

对列表进行排序,现在列表会将重复的值组合在一起。因此,如果当前节点存在副本,则它将是其下一个节点。如果是重复的,删除下一​​个节点。

现在,再次,如果当前节点有副本,它将是它的下一个节点。如此重复上述操作,直到下一个节点不是当前节点的副本。

然后,将下一个节点设为当前节点,并执行相同的过程。

Sort list
current_node = head_node
While current_node != NULL
  If current_node.value == current_node.next.value
    Remove current_node.next
  Else
    current_node = current_node.next

这种方法不保留元素的原始顺序。

同样的例子:

1 -> 2 -> 3 -> 4 -> 1

对列表进行排序:

1 -> 1 -> 2 -> 3 -> 4

我从1开始,查看下一个节点,也是1,发现重复了!删除下一个节点。现在列表是:

1 -> 2 -> 3 -> 4

当前节点仍然是 1。我检查它的下一个节点,它是 2。不是重复的。列表保持不变。设置下一个节点为当前节点。

当前节点是 2。检查它的下一个节点,它是 3,不是重复的。列表保持不变。设置下一个节点为当前节点。

当前节点是 3。检查它的下一个节点,它是 4,不是重复的。列表保持不变。设置下一个节点为当前节点。

当前节点是 4。它没有下一个节点,没有什么要检查的,我已经完成了。列表保持不变:

1 -> 2 -> 3 -> 4

请注意,对于每个节点,我只检查其紧邻的下一个节点。然后,我继续检查最后一个下一个节点。也就是 O(N)。

但是,我必须对列表进行排序,以确保对重复项进行分组。排序列表可以在 O(NlogN) 中完成。

时间复杂度为 O(NlogN) + O(N) = O(NlogN)。

我曾使用合并排序到sort the list in C。还有Remove duplicates from sorted list 进行另一种解释。

【讨论】:

  • 谢谢。我会尽快试试这个。如果我遇到任何事情,我只会将评论回复给您。谢谢!
【解决方案2】:

在发布的代码中,如果您在列表的前面“手动”添加每个节点,那么第一步就是创建一个执行此操作的函数。

然后,您可以创建另一个函数,该函数将在列表中添加一个节点,使其保持排序。它将遍历列表找到正确的位置,并且仅当它不存在另一个具有相同值的节点时才会添加一个节点。

现在您可以遍历原始列表(未排序的列表)并为每个节点尝试将其副本添加到已排序列表中。如果它已经存在,则从原始列表中删除该节点,否则将副本添加到排序列表中。

最后,您将获得两个唯一元素列表,其中一个已排序。

不要忘记创建一个释放分配内存的函数。

【讨论】:

  • 谢谢,显然我不应该使用函数或循环来创建节点,这就是为什么我必须对数字进行硬编码(我没有使用 scanf 作为数字我输入的是我自己独有的)并且它也无法排序,因为这些数字应该是特定的顺序,因为它是唯一的。不过,我会给你一个机会。
【解决方案3】:

看来您的意思是不仅要删除列表中相邻的重复值。

您需要编写一个函数,该函数将通过引用接受列表的头节点。该函数可以返回移除的节点数。

注意声明全局变量是个坏主意

//Global variables
STUDENTIDPtr previousPtr;           //pointer to previous node in list
STUDENTIDPtr currentPtr;            //pointer to current node in list

而且是多余的。

而且这个typedef会迷惑代码的读者

typedef STUDENTID *STUDENTIDPtr; 

您还可以编写一个单独的函数,将一个节点添加到列表中。您最初可以编写函数,使列表中的节点按值排序。

至于删除重复值的函数,它可以如下所示,如下面的演示程序所示。调查一下。

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

/* self-referential structure*/
struct studentID{
    int value;          //a data member which is an integer
    struct studentID *next;         //a data member which is a pointer to next node
};


typedef struct studentID STUDENTID;     //creating a nickname for struct studentID as STUDENTID
typedef STUDENTID *STUDENTIDPtr;  

size_t remove_duplicates( STUDENTIDPtr *head )
{
    size_t n = 0;

    for ( ; *head != NULL; head = &( *head )->next )
    {
        for ( STUDENTIDPtr *next = &( *head )->next; *next != NULL; )
        {
            if ( ( *head )->value == ( *next )->value )
            {
                STUDENTIDPtr tmp = *next;
                *next = ( *next )->next;
                free( tmp );
                ++n;
            }
            else
            {
                next = &( *next )->next;
            }
        }
    }

    return n;
}

void printList(STUDENTIDPtr currentPtr){


    for ( ; currentPtr != NULL; currentPtr = currentPtr ->next )
    {
        printf("%d -> ", currentPtr->value);
    }

    puts( "NULL" );
}

int main(void) 
{
    STUDENTIDPtr newPtr1;           //creating a pointer to create a new node
    STUDENTIDPtr newPtr2;           //creating a pointer to create a new node
    STUDENTIDPtr newPtr3;           //creating a pointer to create a new node
    STUDENTIDPtr newPtr4;           //creating a pointer to create a new node
    STUDENTIDPtr newPtr5;           //creating a pointer to create a new node


    //creation of the first node
    newPtr1 = malloc(sizeof(STUDENTID));            //This is when a node is created
    newPtr2 = malloc(sizeof(STUDENTID));            //This is when a node is created
    newPtr3 = malloc(sizeof(STUDENTID));            //This is when a node is created
    newPtr4 = malloc(sizeof(STUDENTID));            //This is when a node is created
    newPtr5 = malloc(sizeof(STUDENTID));            //This is when a node is created


    newPtr1 -> value = 4; // assign data in first node 
    newPtr1 -> next = newPtr2;


    newPtr2 -> value = 4; // assign data in first node 
    newPtr2 -> next = newPtr3;


    newPtr3 -> value = 5; // assign data in first node 
    newPtr3 -> next = newPtr4;


    newPtr4 -> value = 2; // assign data in first node 
    newPtr4 -> next = newPtr5;


    newPtr5 -> value = 1; // assign data in first node 
    newPtr5 -> next = NULL;


    printList( newPtr1 );

    size_t n = remove_duplicates( &newPtr1 );

    printf( "There are removed %zu elements\n", n );

    printList( newPtr1 );

    return 0;
}

程序输出可能看起来像

4 -> 4 -> 5 -> 2 -> 1 -> NULL
There are removed 1 elements
4 -> 5 -> 2 -> 1 -> NULL

请记住,您还需要编写一个函数来释放列表的所有已分配内存。

【讨论】:

  • 显然我必须为此使用 typedef,而且是的,我很清楚使用全局变量是一种不好的做法。但我首先使用它们是因为我有一个在流程中使用它们的想法,但没有启动它。此外,该功能也可以使用 while 循环而不是 for 循环来完成,对吧?
  • @FAFSHOCK 请注意,只有我才能展示如何编写函数。没有我的代码,你会有很大的困难。L(
  • 是的,谢谢!仍然是新的和正在学习的代码,当涉及到指针和函数等时,它真的让我遇到了麻烦,因为我遇到了许多我真的不知道如何应对的指针错误。我真的不确定是否使用这种方法,因为我还没有了解到您可以以这种方式使用 for 循环(我使用的唯一方法是 for(int i = 0; i&lt;n; i++))。这就是我尝试使用 while 循环的原因
【解决方案4】:

以下解决方案仅适用于足够小的整数值,而不是非常大的整数值。 我们可以在这里做的是另外一个可以作为关联容器的数组。

  • 获取一个数组。将该数组初始化为 0。
  • 开始遍历链表。
  • if (arr[node->data] == 0);然后递增 arr[node->data]。这将使它成为 1,标记一个事件。
  • else if (arr[node->data] == 1);然后删除这个节点,因为这意味着当前节点已经包含一个之前遇到过的整数。

如果您想扩大规模,您可以实现基于散列或 BST 的 DS(类似于 cpp 映射)作为关联容器。

如果您想要一个通用算法,我建议您首先按照以下几行编写一个蛮力逻辑 --

  • 遍历链表
  • 如果是第一次找到“数据”,则忽略并继续。
  • 如果下次找到“数据”,则调用删除逻辑。

【讨论】:

  • 起初使用数组是我的想法。当我运行一个循环时,将单个节点与其他节点进行比较。如果它没有重复项,则将其添加到数组中。但我担心这会是一个漫长的过程。
  • umm.. 不是那样.. 我的意思是 - 如果您的第一个节点包含 1 作为数据,那么 arr[1] = 1 应该完成。移动到链表的下一个节点,如果它再次有一个 1,我们找到了一个重复,因为 arr[1] 已经是 1。在这里,您将只遍历链表一次,并根据使节点中的数据成为数组的索引;并将该索引处的值标记为 1,表示“已经遇到”...
猜你喜欢
  • 2018-09-10
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-11-02
  • 1970-01-01
  • 2015-05-13
  • 2023-03-28
相关资源
最近更新 更多