2

I can't seem to wrap my head around understanding how to assign the head and tail of a doubly linked list. Trying to get the remove function to work to no avail.

I'm fairly certain the problem lies in the LinkedList::insert() and LinkedList::remove() functions, specifically something to do with the head and tail properties of the LinkedList class.

One of the things I don't quite understand how to get it to work is what to assign to the current variable in the LinkedList::remove() function. If I use head, then it assigns the previous node's value. If I use head.next, I get an exception thrown. The other thing is how (if at all) to get previous to work. Also throws an exception.

I've looked at the other examples in StackOverflow, but was unable to find the answers I need.

Node header file

#pragma once

class Node
{
public:
    int data;
    Node* next;
    Node* previous;
};

Class header file

class LinkedList
{
private:
    int length;
    Node* head;
    Node* tail;

public:
    LinkedList();
    void remove(int deleted);
    void insert(int data);
    void display();
    int getLength();
};

LinkedList C++ file

#include <iostream>
#include "LinkedList.h"
#include "Node.h"

using namespace std;

//Define variables used in the class
LinkedList::LinkedList()
{
    length = 0;
    head = NULL;
    tail = NULL;
}

//Define the remove function
void LinkedList::remove(int deletedNode)
{ 
    struct Node* current = head;
    while (current)
    {
        if (current->data == deletedNode)
        {
            if (current->next == NULL)
            {
                current->previous->next = NULL;
                current = NULL;
            }
            else if (head == NULL)
            {
                current->next->previous = NULL;
                current = NULL;
            }
            else
            {
                current->previous->next = current->next;
                current->next->previous = current->previous;
                current = NULL;
            }
        }
        current = current->next;
    }
}

//Define insert function
void LinkedList::insert(int num1)
{
    Node* node = new Node(); //Create new node
    node->data = num1; //Assign new number to node's data variable
    node->next = head; //Assign the current contents of the head variable to the new node's next pointer
    node->previous = tail; //Assign the current contents of the tail variable to the new node's previous pointer
    head = node; //Assign the new node to the head variable
    tail = node->previous;

    length++; //Increase the list's length by one
}

//Define display function
void LinkedList::display()
{
    Node* curr = this->head;
    int i = 1;
    while (curr)
    {
        cout << "Value of node #" << i << " is " << curr->data << endl;
        curr = curr->next;
        i++;
    }
}

//Define getLength function
int LinkedList::getLength()
{
    return length;
}

Main C++ file

#include <iostream>
#include "LinkedList.h"
#include "Node.h"
#include <time.h>

using namespace std;

int main()
{
    int userRemove = 1;

    LinkedList list;

    // Define & start clock
    clock_t start, end;
    start = clock();

    for (int i = 1; i < 101; i++)
    {
        list.insert(rand() % 101);
    }

    // Display list
    list.display();

    // End clock
    end = clock();

    //Display duration it took to display list
    cout << endl << "It took " << (end - start) << " milliseconds to list & display all nodes." << endl;

    //Display total number of nodes
    int len = list.getLength();
    cout << endl << "# of nodes = " << len << endl;

    //Ask user for node number to remove
    while (userRemove != 0)
    {
        cout << endl << "Please enter the number of the node you wish to delete or press '0' to exit: " << endl;
        cin >> userRemove;
        list.remove(userRemove);
        cout << endl << "The first node containing " << userRemove << " has been removed." << endl;
        //Display list and list length after removal
        list.display();
        cout << endl << "# of nodes = " << len << endl;
    }
}
  • 3
    How about using a debugger and setting breakpoints, inspecting variables and finding out where exactly things go wrong? – trincot Mar 29 '23 at 14:50
  • FYI your remove function doesn't delete the relevant node and it doesn't decrement the length of the list. – jarmod Mar 29 '23 at 14:53
  • 2
    [What is a debugger and how can it help me diagnose problems?](https://stackoverflow.com/questions/25385173/what-is-a-debugger-and-how-can-it-help-me-diagnose-problems) – Jesper Juhl Mar 29 '23 at 15:00
  • `insert()` should only set tail if tail is null: `if(tail == NULL)tail = node;`. I would suggest following the style of [std::list](https://en.cppreference.com/w/cpp/container/list) . – rcgldr Mar 29 '23 at 16:20
  • I did use breakpoints and they kept showing `previous` as NULL during several different iterations of the code set. That lead me to believe that `previous` had to be NULL and therefore unnecessary to code at all. The decrementing of the length is indeed something I needed to worry about eventually. I'm just trying to get remove to work. – Sima Elsherif Mar 29 '23 at 23:52
  • Are you tied to having `Node * head; Node * tail`? having sentinel nodes for those can make the member functions easier – Caleth Mar 30 '23 at 09:21
  • @SimaElsherif if you remove the previous member, you have a singly linked list – Caleth Mar 30 '23 at 10:07
  • @Caleth, I'm not entirely sure what you mean. I do need to have some form of pointers to both the head and the tail of the node, but they don't have to be called as such. I don't know what you mean by 'sentinel' nodes. – Sima Elsherif Mar 30 '23 at 16:43
  • Node objects that don't hold data, but mark the ends of the list. A list is doubly linked if you can traverse it in either direction: forwards from the head or backwards from the tail – Caleth Mar 30 '23 at 22:10

5 Answers5

1

--Edit 04/04/2023 -- Seems like it wasn't only my C++ that was rusty. My data structures knowledge has also faded :) See comment by user "@Caleth" down below. What I'm describing here is a "loop". Main difference is that in a double linked list, both Head->Prev and Tail->Next points to NULL (There is nothing before the head or after the tail node).

I'd like to begin by saying I'm a C# and .Net peasant, my c++ knowledge is beyond rusty, so I'll try to make my answer as generic as possible.

Looking at your void LinkedList::insert(int num1) I have a few questions:

What happens with the existing head node (let's call it prevHead) next pointer? prevHead->previous should be set you the node you're inserting

Also, is it necessary to set the tail at all? If you are inserting at the beginning you should only need to set tail->next to the new node you are inserting.

in fact this: node->previous = tail; followed by this: tail = node->previous; doesn't seem to be doing anything at all, you're just swapping variable values back and forth.

Your void LinkedList::remove(int deletedNode) function is interesting. Usually, when removing you'd be specifying an "index" or "position". But seems like you're removing any node with a value that matches the passed deletedNode arg. Is that what you really want?

Remember, one of the biggest advantages of doubly linked lists is that for operations like inserting and deleting you only have to adjust the 'prev' and 'next' pointers of the affected nodes.

Kemuel Sanchez
  • 636
  • 7
  • 13
  • I won't pretend to understand everything you said (and it's me, not you), but I agree that I'm probably making this assignment much harder than it needs to be. I'm just highly unusually very confused with the whole concept of heads, tails, and their pointers. This assignment has definitely made me feel much dumber than I really am. – Sima Elsherif Mar 29 '23 at 23:59
  • The reason I'm setting `tail` is so that I create the doubly part of the linked list. I get the feeling I'm going about that the wrong way, though. – Sima Elsherif Mar 30 '23 at 00:05
  • With the `remove()`, I'm asking the user to enter the value of the node to be deleted, which can be found in the MAIN.CPP file. – Sima Elsherif Mar 30 '23 at 00:06
  • If you are inserting at the "front" of the list, then the only thing you need to do is make the current "tail" point to the new head, e.g.: `tail->next=newHead`. The "doubly" part means that each node is linked to both the previous and next nodes. Hence, double linked. Think of it like a pearl necklace. Each "pearl" or bead is linked both to the previous and to the next "pearl". The last "pearl" (or tail) then links to the first one (head), thus closing the loop. – Kemuel Sanchez Mar 30 '23 at 03:25
  • 1
    @KemuelSanchez no, that's a loop, not a doubly linked list – Caleth Mar 30 '23 at 09:24
0

Since you're doing a doubly-linked list, you forgot to link the former head node back to the newly added node when inserting a new node. As a final step:

if (node->next) {
    node->next->previous = node;
}
Wyck
  • 10,311
  • 6
  • 39
  • 60
  • Doesn't that make the next node's previous pointer point to the current node? – Sima Elsherif Mar 29 '23 at 23:54
  • 1
    @SimaElsherif yes, as it should – Caleth Mar 30 '23 at 09:24
  • @SimaElsherif, indeed it does! Maybe think about it like this. Who is your son's father? ... It's you! OR when considering numbers: N + 1 - 1 = N. If you start at a node, and go "next" one node, then accessing the "previous" of that node should absolutely get you back to the same node where you started. – Wyck Mar 30 '23 at 14:58
  • @Caleth if the next node's previous pointer points to the current node, then how does that help me delete the current node by reassigning the pointers of the previous and next nodes? – Sima Elsherif Mar 30 '23 at 16:45
  • @Wyck That's true if I want things to remain as they are, but I'm trying to learn how to delete nodes by reassigning the pointers of the next and previous nodes' pointers. – Sima Elsherif Mar 30 '23 at 16:47
  • @SimaElsherif your _delete_ code looks okay, but it will only work correctly if the list was built up correctly in the first place, and the doubly-linked list is valid. Hence, the fix required is to your `insert` function. – Wyck Mar 31 '23 at 00:57
0

When you try to delete the node, you don't actually delete anything. You left that node in memory and you lost address of it. Use:

delete current;

Instead of:

current = NULL;

current is in the function scope and it will released automatically. Lastly, don't forget to decrease the length.

To make head or tail you have to check head or tail initialized or not. If the list is empty, head==NULL/tail==NULL/length==0 statements returns true. Before the insert anything check if list empty or not, then decide to initialize head and tail or if they are already initialized just use them.

Here is a good resource to understand data structures.

user16217248
  • 3,119
  • 19
  • 19
  • 37
Thæ
  • 55
  • 5
  • I recently learned about the delete command. I can easily add that instead of `current = NULL`, but that doesn't solve the reassignment of pointers. Or does the delete command take care of that automatically? – Sima Elsherif Mar 30 '23 at 00:08
  • @SimaElsherif It seems you **did not check** the link I shared and you **did not read** my answer completely. – Thæ Apr 02 '23 at 02:31
0

In C++ you should use std::vector for list-like data. If you are absolutely sure that a doubly-linked list is the best fit for your data, then you should use std::list. You should never implement these basic data structures yourself for production code! For learning, of course, it is fine.

Benjamin Buch
  • 4,752
  • 7
  • 28
  • 51
0

insert and remove should only be modifying the two nodes that are adjacent to the target.

To avoid having edge cases, you can have head and tail always exist, as sentinels (with no data).

class LinkedList
{
    Node head;
    Node tail;
    // other members
};

LinkedList::LinkedList() : length(0)
{
    head.next = &tail;
    tail.prev = &head;
}

void LinkedList::insert(int num1)
{
    Node* node = new Node{ num1, head.next, &head };
    head.next = node;
    node->next->prev = node;
    ++length;
}

void LinkedList::remove(int deletedNode)
{
    for (Node * current = head.next; current != &tail; current = current->next)
    {
        if (current->data == deletedNode)
        {
            current->next->prev = current->prev;
            current->prev->next = current->next;
            delete current;
            --length;
            return;
        }
    } 
}
Caleth
  • 52,200
  • 2
  • 44
  • 75