简体   繁体   中英

Reversing a doubly linked list in C

I'm currently trying to reverse a doubly-linked list and print it out in C. Unfortunately, my code is only printing one item and I have no idea why. I have looked at multiple solutions on Stack Overflow and online

Basically, I pass a txt file that looks like this.

Name 100
John 40

Here's the full code:

    #include <stdio.h>
    #include <string.h>
    #include <stdlib.h>
    
    typedef struct person{
      char* firstName;  
      char* age;
      struct person* previous; 
      struct person* next; 
    }Person;
    
    void print(Person*);
    
    void reverseList(Person*);
    
    Person* add (Person*, char*, char*);
    
    void print (Person* list) {
      while(list != NULL) {
        printf("%s %s\n",list->firstName,list->age);
        list = list->next;
      }
    }
    
    void reverseList (Person* list) {
      Person *current = list;
      Person* last = current;
    
      while(current != NULL) {
        Person *temp = NULL;
        temp = current->previous; 
        current->previous = current->next;
        current->next = temp;
        last = current;
        current = current->previous;
      }
      list = last;
    
      while(list != NULL) {
        printf("%s %s\n",list->firstName,list->age);
        list = list->next;
      }
    }
    
    Person* add (Person *list, char *firstName, char *age) {
      // allocate node
      Person* node = (Person*)malloc(sizeof(Person));
    
      // if node is empty
      if(node != NULL) {
        node->firstName=(char *)malloc(sizeof(char)*strlen(firstName)+1);
        
        // insert name
        strcpy(node->firstName, firstName);
    
        // allocate age & insert
        node->age = (char*)malloc(sizeof(char)*strlen(age)+1);
        strcpy(node->age, age);
        node->next = NULL;
    
        if(list == NULL) list = node;
        else {
          Person *curr = list;
          Person *pre = NULL;
          while(curr != NULL) {
            if(strcmp(curr->firstName, firstName) > 0) {
              if(pre == NULL) {
                node->next = list;
                list = node;
              } else {
                pre->next = node;
                node->next = curr;
              }
              return list;
            }
            pre = curr;
            curr = curr->next;
          }
          pre->next = node;
        }
        return list;
      }
      return list;
    }
    
    int main (int argc, char **argv) {
      FILE *in = fopen(argv[1], "r");
      char name[20];
      char num[20];
      Person *list=NULL;
     
      while(fscanf(in, "%s %s",name, num)==2) {
        list = add(list,name, num);
      }
    
      printf ("Original List\n***\n");
      print (list);
      
      printf ("Printing Reversed List\n***\n");
      reverseList(list);
      
      return 0;
    }

The goal is, if I pass a list like:

John 10
Mary 5

It would print

Mary 5
John 10

Any idea what's going on here? Not sure what I am misunderstanding

As noted in my comments, your problems wasn't in your reverseList() function (although it needed help), your problem was in your add() function not setting any of the previous node pointers. So when you got to the end of your list and attempted to print the list in reverse, all you printed was the current node and your print-loop would exit because the ->next node (original ->previous node) was NULL .

To fix the problem, you need to wire-up the ->previous pointers.

( Edit add() rewritten to address sort error on corner cases in original)

Person *add (Person *list, char *firstName, char *age)
{
  size_t len;
  Person *node = malloc (sizeof *node),   /* allocate node, use dereference ptr for size */
         *iter = list,
         *prev = NULL;

  if (!node) {                            /* validate EVERY allocation */
    perror ("malloc-node");
    return list;
  }
  node->next = node->previous = NULL;     /* initialize next and previous NULL */

  len = strlen (firstName);                         /* get length of firstName */
  if (!(node->firstName = malloc (len + 1))) {      /* allocate/validate */
    perror ("malloc-node->firstName");
    free (node);                                    /* free node before return */
    return list;
  }
  memcpy (node->firstName, firstName, len + 1);     /* copy firstName to node */

  len = strlen (age);                               /* get length of age */
  if (!(node->age = malloc (len + 1))) {            /* allocate/validate */
    perror ("malloc-node->age");
    free (node->firstName);                         /* free node->firstName, and */
    free (node);                                    /* free node before return */
    return list;
  }
  memcpy (node->age, age, len + 1);                 /* copy age to node */

  if (!list)                              /* if list empty */
    return node;                          /* return node */

  /* iterate until iter->firstName sorts before firstName */
  while (iter && strcmp (firstName, iter->firstName) <= 0) {
    prev = iter;
    iter = iter->next;
  }

  if (!iter) {                            /* if new end node */
    prev->next = node;
    node->previous = prev;
  }
  else {                                  /* insert between nodes */
    node->next = iter->next;
    node->previous = iter;
    if (iter->next)                       /* protect when new next to last */
      iter->next->previous = node;
    iter->next = node;
  }

  /* if new first node, return iter, otherwise return list */
  return strcmp (list->firstName, firstName) >= 0 ? node : list;
}

Your reverseList() cannot actually "reverse" anything. All it can do is iterate to the last node in the list and then print the list in reverse order using the ->previous pointers. As such, you are needlessly swapping pointers. Simply iterate to the end, turn-around and use the ->previous pointers to iterate back to the beginning printing the list in reverse, eg

void reverseList (Person *list) {

  if (!list) {                  /* validate list not empty */
    puts ("(list empty)");
    return;
  }
  
  while (list->next != NULL)    /* iterte to last node */
    list = list->next;

  while (list) {    /* print in reverse using ->previous pointers */
    printf ("%s %s\n", list->firstName, list->age);
    list = list->previous;
  }
}

It is unfortunate you cannot change the function prototypes. To actually reverse the lined list in a void function, you must pass the address of the list pointer so the node at that address can be updated with the new first node. If you did want to actually reverse the list, you could do:

void reverseList (Person **list) {
  
  Person *current = *list;
  Person *last = current;
  
  if (!current || !current->next)
    return;
  
  do {
    Person *temp = current->previous; 
    current->previous = current->next;
    current->next = temp;
    last = current;
  } while ((current = current->previous));
  *list = last;
}

You would call the above as: reverseList(&list); . Then you would simply call print (list); again to print the list in reverse....

Further, if you could change the prototypes and pass the address of the list pointer to your add() function, you would eliminate the need to keep track of pre and curr , you could simply iterate using both the address of the current node and a pointer to the current node. See Linus on Understanding Pointers

Lastly, do not forget to free the memory you allocate. You must free firstName and age before freeing the node. A simple delete list function to delete all currently allocated memory associated with a list could be written as:

/** delete all nodes in list */
void del_list (Person *list)
{
    while (list) {
        Person *victim = list;
        list = list->next;
        free (victim->firstName);
        free (victim->age);
        free (victim);
    }
}

Let me know if you have further questions.


Full Example

Since the link I provided to the example code in the comments is only good for a month, I'll include the example here for posterity. The example is:

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

typedef struct person{
  char* firstName;  
  char* age;
  struct person* previous; 
  struct person* next; 
}Person;

void print(Person*);

// void reverseList(Person*);

Person* add (Person*, char*, char*);

void print (Person* list) {
  while(list != NULL) {
    printf("%s %s\n",list->firstName,list->age);
    list = list->next;
  }
}

void prnptrs (Person *list) {
    while (list) {
        printf ("%p  %p  %p\n", (void*)list->previous, (void*)list, (void*)list->next);
        list = list->next;
    }
}
#ifndef PASSPTR2PTR
void reverseList (Person *list) {

  if (!list) {                  /* validate list not empty */
    puts ("(list empty)");
    return;
  }
  
  while (list->next != NULL)    /* iterte to last node */
    list = list->next;

  while (list) {    /* print in reverse using ->previous pointers */
    printf ("%s %s\n", list->firstName, list->age);
    list = list->previous;
  }
}
#else
void reverseList (Person **list) {
  
  Person *current = *list;
  Person *last = current;
  
  if (!current || !current->next)
    return;
  
  do {
    Person *temp = current->previous; 
    current->previous = current->next;
    current->next = temp;
    last = current;
  } while ((current = current->previous));
  *list = last;
}
#endif

Person *add (Person *list, char *firstName, char *age)
{
  size_t len;
  Person *node = malloc (sizeof *node),   /* allocate node, use dereference ptr for size */
         *iter = list,
         *prev = NULL;

  if (!node) {                            /* validate EVERY allocation */
    perror ("malloc-node");
    return list;
  }
  node->next = node->previous = NULL;     /* initialize next and previous NULL */

  len = strlen (firstName);                         /* get length of firstName */
  if (!(node->firstName = malloc (len + 1))) {      /* allocate/validate */
    perror ("malloc-node->firstName");
    free (node);                                    /* free node before return */
    return list;
  }
  memcpy (node->firstName, firstName, len + 1);     /* copy firstName to node */

  len = strlen (age);                               /* get length of age */
  if (!(node->age = malloc (len + 1))) {            /* allocate/validate */
    perror ("malloc-node->age");
    free (node->firstName);                         /* free node->firstName, and */
    free (node);                                    /* free node before return */
    return list;
  }
  memcpy (node->age, age, len + 1);                 /* copy age to node */

  if (!list)                              /* if list empty */
    return node;                          /* return node */

  /* iterate until iter->firstName sorts before firstName */
  while (iter && strcmp (firstName, iter->firstName) <= 0) {
    prev = iter;
    iter = iter->next;
  }

  if (!iter) {                            /* if new end node */
    prev->next = node;
    node->previous = prev;
  }
  else {                                  /* insert between nodes */
    node->next = iter->next;
    node->previous = iter;
    if (iter->next)                       /* protect when new next to last */
      iter->next->previous = node;
    iter->next = node;
  }

  /* if new first node, return iter, otherwise return list */
  return strcmp (list->firstName, firstName) >= 0 ? node : list;
}

/** delete all nodes in list */
void del_list (Person *list)
{
    while (list) {
        Person *victim = list;
        list = list->next;
        free (victim->firstName);
        free (victim->age);
        free (victim);
    }
}

int main (int argc, char **argv) {
  FILE *in = argc > 1 ? fopen(argv[1], "r") : stdin;
  char name[20];
  char num[20];
  Person *list=NULL;
 
  while(fscanf(in, "%s %s",name, num)==2) {
    list = add(list,name, num);
  }
  fclose (in);

  printf ("Original List\n***\n");
  print (list);
  puts ("\nPointers\n***");
  prnptrs (list);
  
  printf ("\nPrinting Reversed List\n***\n");
#ifndef PASSPTR2PTR
  reverseList(list);
#else
  reverseList(&list);
  print (list);
#endif
  del_list (list);
  
  return 0;
}

Both versions of (1) your printing the list in reverse, and (2) actually reversing the list are conditionally included by the define of PASSPTR2PTR . If not defined, it will simply pass a pointer to list print the list in reverse, If PASSPTR2PTR is defined, it will pass the address of the list pointer and perform the actual list reversal.

The test data file you posted on pastebin was:

$ cat dat/ypzgttvk.txt
John 12345
Sam 54321
Lion 43222

In both cases the output will be:

$ ./bin/lld_reverse_add dat/ypzgttvk.txt
Original List
***
John 12345
Lion 43222
Sam 54321

Pointers
***
(nil)  0xb014a0  0xb01580
0xb014a0  0xb01580  0xb01510
0xb01580  0xb01510  (nil)

Printing Reversed List
***
Sam 54321
Lion 43222
John 12345

Which shows the original list, the addresses from all previous current next nodes for each node in the list and finally the output of the list reversed.

Let me know if you have any further questions.

The code actually seems to be working fine. I'm thinking your test case is not coded correctly, perhaps you forgot to set mary->previous = john (or john->next = mary )

I ran

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

typedef struct person{
  char *firstName;  
  int age; 
  struct person *previous;
  struct person *next;
}Person;

void reverseList (Person *list) {
  Person* current = list;
  Person* last = current;

  while(current != NULL) {
    Person* temp = NULL;
    temp = current->previous;
    current->previous = current->next;
    current->next = temp;
    last = current;
    current = current->previous;
  }
  list = last;

  while(list != NULL) {
    printf("%s %d\n",list->firstName,list->age);
    list = list->next;
  }
}


int main(void) {
    struct person *john = (struct person*)malloc(sizeof(Person));
    struct person *mary = (struct person*)malloc(sizeof(Person));
    john->next = mary;
    mary->previous = john;

    john->age = 10;
    john->firstName = "john";

    mary->age = 5;
    mary->firstName = "mary";

    reverseList(john);

}

and the result was

mary 5
john 10

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM