Implementation: Single linked list¶

In this lecture, we will develop a single linked list which is a simplification of the std::list. Note that the std::list is a double linked list which allows forward and backwards iteraitons over the list. For simplificaiton we use the single linked list and only implement the forward iterator. However, adding the backward iterator is not much more additional work.

alt text

Datastructure¶

Each element of type T is stored in a struct/class data and inside there is stored

  • T element which is the element itself
  • A pointer to the struct/class
template<typename T>
struct data{

data(T in){

element = in;
}

// Element of the type std::list<T>
T element;
// Pointer of type of the class/struct
data<T>* next = nullptr;

};
// Struct for our implementation of the list
template<typename T>
struct myList{

//Pointer to the first element
data<T>* first = nullptr;

myList(){}

myList(T in){

    first = new data<T>(in);

}

bool empty(){

    if (first == nullptr)
        return true;

    return false;
}

};

Initilaziation of a list¶

alt text

In [4]:
#include<list>
#include<iostream>
Out[4]:

In [5]:
// CST list
std::list<double> list;

// Our list
myList<double> mylist;
Out[5]:

In [6]:
std::cout<< list.empty() << " " << mylist.empty() << std::endl;
1 1
Out[6]:

In [7]:
list = {1};

mylist = myList<double>(1);
Out[7]:

In [8]:
mylist.print();
1
Out[8]:

Adding to the list¶

For a linked list, we always have to keep the pointer to the first element, since we use this pointer to access all other elements of the list. The pointer to the second element will be added to the next pointer of the first element.

alt text

In [9]:
list.push_back(2);

mylist.push_back(2);
Out[9]:

Implemententing the push_back function¶

void push_back(T element){

data<T>* tmp = first;

while (tmp->next != nullptr)
    tmp = tmp->next;

tmp->next = new data<T>(element);
}

Implementing the push_front function¶

alt text

void push_front(T element){

data<T>* tmp = first;

first = new data<T>(element);

first->next = tmp; 

}
In [10]:
list.push_front(-1);
mylist.push_front(-1);
mylist.print();
-1 1 2
Out[10]:

Printing the elements¶

In [11]:
#include<iostream>
Out[11]:

In [12]:
void print(std::list<double> list){
std::list<double>::iterator it;
for (it = list.begin(); it != list.end(); it++)
    std::cout << *it << " " ;

}
Out[12]:

In [13]:
print(list);
-1 1 2 
Out[13]:

void print(){

data<T>* tmp = first;

while (tmp->next != nullptr){
    std::cout << tmp->element << " ";
    tmp = tmp->next;
    }
    std::cout << tmp->element;
}
In [14]:
mylist.print();
-1 1 2
Out[14]:

Remove the last element¶

alt text

void pop_back(){

data<T>* tmp = first;
data<T>* prev;

while (tmp->next->next != nullptr){
    tmp = tmp->next;
    }

    delete tmp->next;
    tmp->next = nullptr;
}
In [15]:
list.pop_back();
print(list);
std::cout << std::endl;
mylist.pop_back();
mylist.print();
-1 1 
-1 1
Out[15]:

Inserting a element¶

void insert(T element, size_t index){

data<T>* newNode = new data<T>(element);
data<T>* tmp = first;
data<T>* prev = nullptr;


//Case: Replace the head node
if (index == 0 && tmp != nullptr){
    newNode->next = tmp;
    *first = newNode;
    return;
}

//tmp = tmp->next;

// Case: search for the node
size_t i = 0;

while(i < index && tmp != nullptr){

    prev = tmp;
    tmp = tmp->next;
    i++;
}
if (tmp == nullptr)
    {
    std::cout << "Index " << index << " out of range" << std::endl;
    return;
    }

    prev->next = newNode;    
    newNode->next = tmp;
}
In [16]:
std::cout << "Initial list" << std::endl;
mylist.print();
std::cout << std::endl;
std::cout << "Inserting at the beginning" << std::endl;
mylist.insert(1.0,0);
mylist.print();
std::cout << std::endl;
std::cout << "Inserting somewhere" << std::endl;
mylist.insert(40.0,1);
mylist.print();
std::cout << std::endl;
mylist.insert(40.0,10);
Initial list
-1 1
Inserting at the beginning
1 -1 1
Inserting somewhere
1 40 -1 1
Index 10 out of range
Out[16]:

Deleting a element¶

void remove(T element){

data<T>* tmp = first;
data<T>* prev = nullptr;

// Case I: Delete the head node
if (tmp != nullptr && tmp->element == element){

    first = tmp->next;
    delete tmp;
    return;
    }

while (tmp != nullptr && tmp->element != element) 
    { 
        prev = tmp; 
        tmp = tmp->next; 
    } 

// Node was not found 
if (tmp == nullptr) 
        return; 

// Unlink the node from linked list 
prev->next = tmp->next; 

// Free memory 
delete tmp; 

}
In [17]:
mylist.remove(2.0);
mylist.print();
1 40 -1 1
Out[17]:

Finding a element¶

bool find(T element){

bool found = false;

data<T>* tmp = first;

while (tmp != nullptr){

    if (tmp->element == element)
    return true;

    tmp = tmp->next;
    }

return false;

}

Example¶

In [18]:
#include <algorithm>
Out[18]:

In [19]:
auto p = std::find(list.begin(),list.end(),5.0);
Out[19]:

In [20]:
if ( p != list.end())
{
 std::cout << "Element " << *p << " was found!\n";
}
else
{
    std::cout << "Element was not found!\n"; 
}
Element was not found!
Out[20]:

In [21]:
p = std::find(list.begin(),list.end(),1.0);
Out[21]:

In [22]:
if ( p != list.end())
    std::cout << "Element " << *p << " was found!\n";
else
    std::cout << "Element was not found!\n"; 

std::cout << mylist.find(5.0) << std::endl;
std::cout << mylist.find(1.0) << std::endl;
Element 1 was found!
0
1
Out[22]: