C++ Deep Copying Linked List - c++

First of all, this is part of an assignment I'm currently trying to figure out. I'm trying to create a copy constructor that deep copies a given LinkedList.
I have coded the LinkedList methods already.
Here's the necessary parts of the LinkedList.h file.
LinkedList.h
private:
struct node {
Val data;
node* next = nullptr;
};
typedef struct node* nodePtr;
nodePtr head = nullptr;
nodePtr current = nullptr;
nodePtr temp = nullptr;
};
The parameters are given: "LinkedList::LinkedList(const LinkedList & ll)"
ll is the linked list to be copied.
I first tested if there is a head in the linked list, if not then that means the linked list is empty.
Then I copied the head from the old list to the new list.
I then set the new current to the head in preparation for the while loop.
Inside the while loop, I am copying the data of the current node as well as the pointer to the next node.
At the end I set the next pointer to nullptr to signify the end of the new list.
LinkedList.cpp
LinkedList::LinkedList(const LinkedList & ll){
if (ll.head == nullptr) {
return;
}
head = ll.head;
current = head;
while (ll.current->next != nullptr) {
current->data = ll.current->data;
current->next = ll.current->next;
}
current->next = nullptr;
}
I'm not sure if this is deep copying or not.
I also know that ll.current's starting position is not at the head.
I tried ll.current = ll.head. However, since it is given that this function is const. I can't set it like that.
There is also another function given:
LinkedList & LinkedList::operator=(const LinkedList & ll)
{
}
That I suspect may be needed. I'm hoping it optional that I use this.

You need to allocate new memory or new list elements as you add them, change your code to do the following:
// LinkedList.cpp
LinkedList::LinkedList(const LinkedList & ll)
{
if (ll.head == nullptr)
return;
// Create a temp variable since ll.current doesn't move/change.
node* tmp = ll.head;
// Allocate a new node in memory.
head = new node;
// Copy over the value.
head->data = tmp->data;
// Set the 'next' value to null (the loop will fill this in).
head->next = nullptr;
// Point 'current' to 'head'.
current = head;
// Move to next item in ll's list.
tmp = tmp->next;
while (tmp != nullptr)
{
// Allocate new memory for a new 'node'.
current->next = new node;
// Point to this new 'node'.
current = current->next;
// Copy over the data.
current->data = tmp->data;
// By default set the 'next' to null.
current->next = nullptr;
// Move along ll's list.
tmp = tmp->next;
}
}
Also, in your class get rid of typedef node* nodePtr. There is no need for that, it's cleaner to simply use node* for head, current and temp. Lastly, don't forget in your class' destructor to clear out dynamically allocated memory:
LinkedList::~LinkedList()
{
current = head;
while(current != nullptr)
{
current = current->next;
delete head;
head = current;
}
}

This cannot work, as you never allocate new list elements for the actual list object (using the 'new' operator), but only reuse existing ones. Just think about what happens, if ll has more elements than the actual list?

Related

Optimizing the popBack method for your own list implementation

I am writing my own implementation of a single linked list. I wrote a popBack() method, but it seems to me that it is too overloaded with conditional operators, which should affect performance (and in general, it looks so-so). Is it possible to optimize it somehow so that it does not look so terrible?
void popBack() {
if (head == nullptr) {
return;
}
if (head->next == nullptr) {
delete head;
head = nullptr;
}
else {
Node* prev = nullptr;
Node* curr = head;
while(curr->next != nullptr) {
prev = curr;
curr = curr->next;
}
if(prev != nullptr) {
delete curr;
prev->next = nullptr;
}
}
size--;
}
The 2nd if block is not necessary. Also, the else block can be simplified if you use a Node** pointer to point at the Node* variable which needs to be null'ed when the Node object it is pointing at is delete'd, eg:
void popBack() {
if (!head) return;
Node *curr = head, **prev = &head;
while (curr->next) {
prev = &(curr->next);
curr = *prev;
}
*prev = nullptr;
delete curr;
--size;
}
Of course, if you really want to optimize the popping, you should implement a double-linked list instead, and add a tail member to your list class to point at the last Node in the list, then you won't need a while loop to hunt for the trailing Nodes in the list, eg:
void popBack() {
if (!tail) return;
Node *curr = tail;
tail = tail->previous;
Node **n = (tail) ? &(tail->next) : &head;
*n = nullptr;
delete curr;
--size;
}
One technique that can clean the code up a lot is to ensure the list always includes one node that you basically treat as empty. You never use the value it contains, but it eliminates many of the corner cases, so most of the code never had to deal with them.
Using this, pop_back can end up something like this:
void pop_back() {
if (head->next == nullptr)
return; // empty list--nothing to do.
// traverse to just before the sentinel node:
node* loc;
for (loc = head; loc->next->next != nullptr; loc = loc->next)
;
// at this point, `loc` points to the last "real" node in the list
// delete the old sentinel node
// then turn this into the sentinel node by setting its next pointer
// to a null pointer:
delete loc->next;
loc->next = nullptr;
}
Of course, to make this work, we have to assure the list always contains the sentinel node:
LinkedList() {
// node with its next pointer set to a null pointer:
head = new node(nullptr);
}
...and (of course) push_back has to be aware of the sentinel as well:
void push_back(T t) {
if (head->next == nullptr) {
head = new node(t, head);
return;
}
node* loc;
for (loc = head; loc->next->next != nullptr; loc = loc->next)
;
loc->next = new node(t, loc->next);
}
We can also carry this a step further by saving the location of the sentinel node. Although we still need to traverse the list to do a pop_back, we can avoid it when we want to do a push_back (we put the new value into the current sentinel node, then allocate a new sentinel node, and update our tail pointer to its location.
This general approach is, however, restricted to types that we don't mind creating a default-constructed item that we don't (currently) use. In my experience that's true a lot more often than not, but there are exceptions for which this design isn't suitable.

Confused on how to do a deep copy of a doubly Linked List?

I have been trying to implement a deep copy of a doubly linked list, but I have been having trouble with it. I have to do it different several times, but I ended up getting an address error. I just need an explanation how to do it properly.
List.H
class List
{
public:
List();
~List();
List(const List& c);
List& operator= (const List& t);
private:
List *Next;
List *Prev;
Node *Head;
List.cpp
List::~List()
{
Node* move = Head;
while (move!=NULL)
{
Node *temp = move->Next;
delete move;
move = temp;
}
}
List::List(const List& c)
{
name = c.name;
if (c.Head == NULL) {
Head = NULL;
}
else {
Head = new Node(*c.Head);
Node* Current = Head;
Node* ObjHead = c.Head;
Node* CurrentObj = ObjHead;
while (Current->Next!=NULL) {
Current->Next = new Node (CurrentObj->Next->condiments);
}
}
}
Copying linked lists is about three things:
Traversing the list being copied.
Making copies of new nodes from the originals.
For each new node from (2), tail-link it to the linked list.
The first of these is trivial, the second is fairly basic, but the third is the one that often tosses people for a loop. For your copy-ctor, one way to do it employs a pointer-to-pointer. This allows us to address each pointer in our linked list by their own addresses.
List::List(const List& c)
: Head(nullptr)
, name(c.name)
{
Node *prev = nullptr;
Node **pp = &Head;
for (const Node *p = c.Head; p; p = p->Next)
{
// make a new node, storing its address at the
// pointer obtained by dereference of `pp`. the
// first iteration that will be the Head member.
*pp = new Node(*p);
(*pp)->Prev = prev;
prev = *pp;
// now just advance `pp` to point to the `Next`
// member of the node we just hung on the list.
pp = &(*pp)->Next;
}
*pp = nullptr; // terminate the list.
}
This assumes you're Node class supports copy-construction (it had better). but that's all it takes. From that, you can use the copy/swap idiom to manufacture your copy-assignment operator and have a basic rule-of-three compliance list class.
You can use a dummy head to begin with. Once the deep copying is done, you can set head to the next of the dummy head and delete the dummy head. You also won't have to check for if (c.Head == NULL) this way.
Node *ptr1, *ptr2;
head = ptr1 = new Node();
ptr2 = c.head;
while (ptr2)
{
ptr1->next = new Node(*ptr2);
ptr1 = ptr1->next;
ptr2 = ptr2->next;
}
Node *temp = head;
head = head->next;
delete temp;

Linked List pushback member function implementation

I am a novice programmer and this is my second question on Stack Overflow.
I am trying to implement a pushback function for my Linked List by using a tail pointer. It seems straightforward enough, but I have a nagging feeling that I am forgetting something or that my logic is screwy. Linked Lists are hard!
Here is my code:
template <typename T>
void LinkedList<T>::push_back(const T n)
{
Node *newNode; // Points to a newly allocated node
// A new node is created and the value that was passed to the function is stored within.
newNode = new Node;
newNode->mData = n;
newNode->mNext = nullptr;
newNode->mPrev = nullptr;
//If the list is empty, set head to point to the new node.
if (head == nullptr)
{
head = newNode;
if (tail == nullptr)
{
tail = head;
}
}
else // Else set tail to point to the new node.
tail->mPrev = newNode;
}
Thank you for taking the time to read this.
Your pointing the wrong mPrev to the wrong node. And you never set mNext of the prior tail node if it was non-null to continue the forward chain of your list.
template <typename T>
void LinkedList<T>::push_back(const T n)
{
Node *newNode; // Points to a newly allocated node
// A new node is created and the value that was passed to the function is stored within.
newNode = new Node;
newNode->mData = n;
newNode->mNext = nullptr;
newNode->mPrev = tail; // may be null, but that's ok.
//If the list is empty, set head to point to the new node.
if (head == nullptr)
head = newNode;
else
tail->mNext = newNode; // if head is non-null, tail should be too
tail = newNode;
}

Is it necessary to use 'new' to allocate a new pointer to a struct identical to a previously existing pointer to the same struct? If so, why?

I am writing a function that inserts a new element in a singly linked linked list.
The function accepts the *head to the list, the data to be put in the new element and the position in the l-list should be inserted at.
This is the link to the programming exercise in case I've not been clear enough.
The following code works perfectly -
/*
Insert Node at a given position in a linked list
head can be NULL
First element in the linked list is at position 0
Node is defined as
struct Node
{
int data;
struct Node *next;
}
*/
Node* InsertNth(Node *head, int data, int position)
{
//Takes head of list as input and returns it after inserting a new element
//with `data` at `position` in the l-list
Node *temp = new Node;
temp = head;
Node *newn = new Node;
newn->data = data;
if(position == 0){
newn->next = head;
return newn;
}
while(--position){
temp = temp->next;
}
newn->next = temp->next;
temp->next = newn;
return head;
}
However, I don't understand why I had to use Node *temp = new Node; temp = head;.
Why doesn't simply using Node *temp = head; work?
I'm only using the temp pointer to traverse the l-list so that I can preserve the location of the head when I return the final list.
Edit - I understand that Node *temp = head; is the way to go. This was how I originally programmed it too, but I forgot to mention that this is what's giving me a segfault. When I change it to Node *temp = new Node; temp = head; it works for all the testcases(including the ones where head is NULL).
Why must this seemingly absurd mem allocation seem to make it work is what I want to know.
The code you post above leaks.
Node *temp = new Node; temp = head;
This is no good.
Node *temp = head;
This is better.
There are other problems in your code; but your analysis that it was silly to new then immediately reassign the pointer is correct. Well spotted.
The answers that were posted before mine are both wrong, sure they Point out that your code leaks, but they didn´t check the rest of the code to see if it actually does what it is supposed to do. Your code is prone to Errors, because you don´t account for the head being NULL, which is clearly stated.
// Returns the new node inserted at the given Position inside the l-list
Node* InsertNth(Node *head, int data, int position)
{
Node *newn = new Node;
newn->data = data;
newn->next = 0;
// No head, return node right away
if(!head)
{
return newn;
}
// Exception - only case where head is not returned
else if(!position)
{
newn->next = head;
return newn;
}
// Create ´temp´ which is a pointer to the next node in the list
Node *temp = head;
// The function allows passing of a signed int, make sure we stay above 0
// as the previously while(--position) would go into an endless loop
// if a signed integer were passed on to the function
while(--position > 0)
{
// Just incase the input is bad, and position exceeds the size of the list
if(!temp->next)
{
break;
}
temp = temp->next;
}
// Now that we found the place in line, insert it between the temp and the next item
// in the list. Which may also be NULL
newn->next = temp->next;
temp->next = newn;
return head;
}
Now as you can see a few changes were made, the code earlier on didn´t account for head being NULL, and it didn´t properly set the member ´next´ to 0, which would lead to a Crash eventually if someone iterated over the supposedly null-terminated single-linked list.
Use of
Node *temp = new Node;
temp = head;
causes a memory leak. Memory allocated in the first line is lost. You need to use:
Node *temp = head;

Deep copy and deconstructor sentinel linked list using Nodes

I am currently making a linked List program using Nodes(not that i know of any other way) and I have come upon a problem about creating a deep copy and getting rid of all my Nodes and Sentinels with my ~List(). Deleting the Nodes is not a problem, but the sentinels are since the first one is not assigned a index value.
List::~List()
{
for(size_t i=0; i<size; i++)
{
_setCurrentIndex(i);
if(current && curent->next == NULL)
{
Node *temp = current->next;
delete temp;
delete current;
}
else
{
Node *old = current;
current = current->next;
delete old;
}
}
}
List::List(const List & orig)
{
for(size_t i=0; i<size; i++)
{
if(i==0)
{
Node *copyFront = new Node; //the first sentinel
copyFront->data = orig.front->data; //front is defined in private in list.h
copyFront->prev = NULL; // it is defined as a Node (same for rear)
}
else if(0<=i && i<size) //put in i<size b/c 0<=i would always be true
{
_setCurrentIndex(i) //sets what current is and currentIndex which pts to diff Nodes
Node *copy = new Node;
copy->data = current->data;
copy->next = current->next;
current = current->next;
}
else if(i+1 == size)
{
Node *copyRear = new Node; //making the last sentinel, but it has to be
copyRear->data = orig.rear->data; //after data Node
copyRear->next = NULL;
}
}
}
I am seeking advice and comments on this code on how to proceed next or what to change if something is dreadfully wrong!
Linked lists are templates allowing any type of variable to sit in them. In my honest opinion, you'd be best off using the std::list which requires the #include <list> header file.
Of course, if you really want the experience of writing a linked list class yourself then the following code makes a deep copy of a list:
List::List( const List& other) {
if( other.head_ != nullptr) {
head_ = new Node( other.head_->item_); // copy first node
assert( head_ != nullptr); // ensure that the memory was allocated correctly
// copy the rest of the list
Node* pnew = head_;
// loop through the list until you reach the end (i.e. a node that's nullptr)
for( Node* porig( other.head_->next_); porig != nullptr; porig = porig->next_) {
// assign the next node in the destination list to the next node in the paramter's list
pnew->next_ = new Node( porig->item_);
assert( pnew->next_ != nullptr); // ensure that the memory was allocated correctly
pnew = pnew->next_; // move onto the newly created node in the destination list
}
}
else
// if the parameter is empty then the destination list will be empty as well
head_ = nullptr;
}
As for the destructor, you just need to run through the list deleting the nodes as you go:
List::~List() {
while( head_ != nullptr) { // keep looping until the list gets to the end
// make a second pointer to the node you are about to delete (so you don't lose track of it)
Node* pn( head_);
// move the head_ onto the next node essentially "removing" the first node from your list
head_ = head_->next_;
// delete the node that you've just "removed" from your list
delete pn;
}
}
I've tried to make the comments clear up anything that might be unclear.