我有一个链表,我想删除重复的值

时间:2019-11-20 12:05:56

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

因此,我创建了一个代码,在其中创建了一个包含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并遍历每个链表会很容易还是有更好的方法?

4 个答案:

答案 0 :(得分:3)

马上想到两种方法,哪种方法取决于您的具体情况,以及是否要保留元素的原始顺序。


第一种方法:

使用双循环并一次拾取一个节点。然后迭代该节点之后的列表,如果找到重复的列表,则将其删除。重复选择节点,直到遍历整个列表。

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,没有发现重复项。列表保持不变。

现在,我正在寻找第四个节点的副本。下一个节点为NULL,这意味着第四个节点是最后一个节点(因为我们在第一个遍历中删除了第五个节点,重复的值为1)。然后没有任何检查,列表保持不变:

  

1-> 2-> 3-> 4

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

我有多少个节点? N

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

我强烈建议您尝试一下并进行练习。完成后,您可以阅读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供其他说明。

答案 1 :(得分:1)

在发布的代码中,您是“手动”添加列表顶部的每个节点,因此第一步是创建一个执行此操作的函数。

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

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

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

别忘了创建释放分配的内存的函数。

答案 2 :(得分:1)

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

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

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

//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

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

答案 3 :(得分:0)

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

  • 获取一个数组。将该数组初始化为0。
  • 开始遍历链表。
  • if(arr [node-> data] == 0);然后增加arr [node-> data]。这将使它成为1,表示发生了一次。
  • 否则if(arr [node-> data] == 1);然后删除该节点,因为这意味着当前节点已经包含一个以前遇到过的整数。

如果您想扩大规模,则可以基于散列或BST实现DS,类似于cpp映射,以充当关联容器。

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

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