Home > Software design >  Removing unique elements in a doubly linked list in C
Removing unique elements in a doubly linked list in C

Time:11-11

I need a little help removing unique characters in a doubly linked list in C. So here's the logic I tried implementing: I counted the occurrence of each character in the doubly linked list. If it's occurrence is 1 time, then it is unique element and needs to be deleted. I'll be repeating the process for all elements. But my code in remove_unique_dll() function isn't working properly, please help me fix it. Here's my code-

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

struct node
{
    char data;
    struct node *next;
    struct node *prev;
};

struct node *head, *tail = NULL; //Represent the head and tail of the doubly linked list  
int len;

void addNode(char data)
{
    struct node *newNode = (struct node*) malloc(sizeof(struct node)); //Create new node  
    newNode->data = data;

    if (head == NULL)
    {      //If dll is empty  
        head = tail = newNode;  //Both head and tail will point to newNode  
        head->prev = NULL; //head's previous will point to NULL  
        tail->next = NULL; //tail's next will point to NULL, as it is the last node of the list  
    }
    else
    {
        tail->next = newNode; //newNode will be added after tail such that tail's next points to newNode  
        newNode->prev = tail; //newNode's previous will point to tail  
        tail = newNode;  //newNode will become new tail  
        tail->next = NULL; //As it is last node, tail's next will point to NULL  
    }
}

void remove_unique_dll()
{
    struct node *current = head;
    struct node *next;
    struct node *prev;
    int cnt;

    while (current != NULL)
    {
        next = current->next;
        cnt = 1;

        //printf("!%c ",next->data);

        while (next != NULL)
        {
            if (next->data == current->data)
            {
                cnt  = 1;
                next = next->next;
            }
            else
                next = next->next;

            //printf("@%c %d %c\n",next->data,cnt,current->data);
        }

        if (cnt == 1)
        {
            prev = current->prev;
            //printf("@%c %d",prev->data,cnt);

            if (prev == NULL)
            {
                head = next;
            }
            else
            {
                prev->next = next;
            }

            if (next == NULL)
            {
                tail = prev;
            }
            else
            {
                next->prev = prev;
            }

        }
        current = current->next;
        //printf("#%c ",current->data);
    }

    head = current;
}

void display()
{
    struct node *current = head;  //head the global one

    while (current != NULL)
    {
        printf("%c<->", current->data); //Prints each node by incrementing pointer.  
        current = current->next;
    }

    printf("NULL\n");
}

int main()
{
    char s[100];
    int i;

    printf("Enter string: ");
    scanf("%s", s);

    len = strlen(s);
    for (i = 0; i < len; i  )
    {
        addNode(s[i]);
    }

    printf("Doubly linked list: \n");
    display();

    remove_unique_dll();

    printf("Doubly linked list after removing unique elements: \n");
    display();

    return 0;
}

The output is like this- enter image description here

If you uncomment the printf() statements inside remove_unique_dll() you'll notice that no code below inner while loop is being executed after inner while loop ends. What's the issue here and what's the solution?

Sample input- aacb

Expected output- a<->a<->NULL

CodePudding user response:

You have at least three errors.

After counting the number of occurrences of an item, you use next in several places. However, next has been used to iterate through the list. It was moved to the end and is now a null pointer. You can either reset it with next = current->next; or you can change the places that use next to current->next.

At the end of remove_unique_dll, you have head=current;. There is no reason to update head at this point. Whenever the first node was removed from the list, earlier code in remove_unique_dll updated head. So it is already updated. Delete the line head=current;.

That will leave code that deletes all but one occurrence of each item. However, based on your sample output, you want to leave multiple occurrences of items for which there are multiple occurrences. For that, you need to rethink your logic in remove_unique_dll about deciding which nodes to delete. When it sees the first a, it scans the remainder of the list and sees the second, so it does not delete the first a. When it sees the second a, it scans the remainder of the list and does not see a duplicate, so it deletes the second a. You need to change that.

CodePudding user response:

Some issues:

  • You shouldn't assign head = current at the end, because by then current is NULL

  • The next you use in the deletion part is not the successor of current, so this will make wrong links

  • As you progress through the list, every value is going to be regarded as unique at some point: when it is the last occurrence, you'll not find a duplicate anymore, as your logic only looks ahead, not backwards.

  • When you remove a node, you should free its memory.

  • Not a big issue, but there is no reason to really count the number of duplicates. Once you find the first duplicate, there is no reason to look for another.

You should really isolate the different steps of the algorithm in separate functions, so you can debug and test each of those features separately and also better understand your code.

Also, to check for duplicates, you might want to use the following fact: if the first occurrence of a value in a list is the same node as the last occurrence of that value, then you know it is unique. As your list is doubly linked, you can use a backwards traversal to find the last occurrence (and a forward traversal to find the first occurrence).

Here is some suggested code:

struct node* findFirstNode(char data) {
    struct node *current = head;
    while (current != NULL && current->data != data) {
        current = current->next;
    }
    return current;
}

struct node* findLastNode(char data) {
    struct node *current = tail;
    while (current != NULL && current->data != data) {
        current = current->prev;
    }
    return current;
}

void removeNode(struct node *current) {
    if (current->prev == NULL) {
        head = current->next;
    } else {
        current->prev->next = current->next;
    }
    if (current->next == NULL) {
        tail = current->prev;
    } else {
        current->next->prev = current->prev;
    }
    free(current);
}

void remove_unique_dll() {  
    struct node *current = head;
    struct node *next;

    while (current != NULL)
    {
        next = current->next;
        if (findFirstNode(current->data) == findLastNode(current->data)) {
            removeNode(current);
        }
        current = next;
    }
}

CodePudding user response:

Let's consider your code step by step.

It seems you think that in this declaration

struct node *head, *tail = NULL; //Represent the head and tail of the doubly linked list  

the both pointers head and tail are explicitly initialized by NULL. Actually only the pointer tail is explicitly initialized by NULL. The pointer head is initialized implicitly as a null pointer only due to placing the declaration in file scope. It to place such a declaration in a block scope then the pointer head will be uninitialized.

Instead you should write

struct node *head = NULL, *tail = NULL; //Represent the head and tail of the doubly linked list  

Also it is a very bad approach when the functions depend on these global variables. In this case you will be unable to have more than one list in a program.

Also the declaration of the variable len that is used only in main as a global variable

int len;

also a bad idea. And moreover this declaration is redundant.

You need to define one more structure that will contain pointers head and tail as data members as for example

struct list
{
    struct node *head;
    struct node *tail;
};

The function addNode can invoke undefined behavior when a new node can not be allocated

void addNode(char data)
{
    struct node *newNode = (struct node*) malloc(sizeof(struct node)); //Create new node  
    //...

You should check whether a node is allocated successfully and only in this case change its data members. And you should report the caller whether a node is created or not.

So the function should return an integer that will report an success or failure.

In the function remove_unique_dll after this while loop

    while (next != NULL)
    {
        if (next->data == current->data)
        {
            cnt  = 1;
            next = next->next;
        }
        else
            next = next->next;

        //printf("@%c %d %c\n",next->data,cnt,current->data);
    }

if cnt is equal to 1

    if (cnt == 1)
    //..

then the pointer next is equal to NULL. And using the pointer next after that like

        if (prev == NULL)
        {
            head = next;
        }
        else
        {
            prev->next = next;
        }

is wrong.

Also you need to check whether there is a preceding node with the same value as the value of the current node. Otherwise you can remove a node that is not a unique because after it there are no nodes with the same value.

And this statement

head = current;

does not make sense because after the outer while loop

while (current != NULL)

the pointer current is equal to NULL.

Pay attention that the function will be more useful for users if it will return the number of removed unique elements.

Here is a demonstration program that shows how the list and the function remove_unique_dll can be defined.

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

struct node
{
    char data;
    struct node *next;
    struct node *prev;
};

struct list
{
    struct node *head;
    struct node *tail;
};

int addNode( struct list *list, char data )
{
    struct node *node = malloc( sizeof( *node ) );
    int success = node != NULL;

    if (success)
    {
        node->data = data;
        node->next = NULL;
        node->prev = list->tail;

        if (list->head == NULL)
        {
            list->head = node;
        }
        else
        {
            list->tail->next = node;
        }

        list->tail = node;
    }

    return success;
}

size_t remove_unique_dll( struct list *list )
{
    size_t removed = 0;

    for ( struct node *current = list->head; current != NULL; )
    {
        struct node *prev = current->prev;

        while (prev != NULL && prev->data != current->data)
        {
            prev = prev->prev;
        }

        if (prev == NULL)
        {
            //  there is no preceding node with the same value
            //  so the current node is possibly unique

            struct node *next = current->next;
            while (next != NULL && next->data != current->data)
            {
                next = next->next;
            }

            if (next == NULL)
            {
                // the current node is indeed unique

                struct node *to_delete = current;
                if (current->prev != NULL)
                {
                    current->prev->next = current->next;
                }
                else
                {
                    list->head = current->next;
                }

                if (current->next != NULL)
                {
                    current->next->prev = current->prev;
                }
                else
                {
                    list->tail = current->prev;
                }

                current = current->next;

                free( to_delete );

                  removed;
            }
            else
            {
                current = current->next;
            }
        }
        else
        {
            current = current->next;
        }
    }

    return removed;
}

void display( const struct list *list )
{
    for (const node *current = list->head; current != NULL; current = current->next)
    {
        printf( "%c<->", current->data );
    }

    puts( "null" );
}

int main()
{
    struct list list = { .head = NULL, .tail = NULL };

    const char *s = "aabc";

    for (const char *p = s; *p != '\0';   p)
    {
        addNode( &list,  *p );
    }

    printf( "Doubly linked list:\n" );
    display( &list );

    size_t removed = remove_unique_dll( &list );

    printf( "There are removed %zu unique value(s) in the list.\n", removed );

    printf( "Doubly linked list after removing unique elements:\n" );
    display( &list );
}

The program output is

Doubly linked list:
a<->a<->b<->c<->null
There are removed 2 unique value(s) in the list.
Doubly linked list after removing unique elements:
a<->a<->null

You will need at least to write one more function that will free all the allocated memory when the list will not be required any more.

  • Related