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]:
(std::basic_ostream<char, std::char_traits<char> >::__ostream_type &) @0x7f71cc841500
In [7]:
list = {1};

mylist = myList<double>(1);
Out[7]:
(myList &) @0x7f71cf392030
In [8]:
mylist.print();
1
Out[8]:
(void) nullptr

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]:
(void) @0x7f71bac79c88

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]:
(void) @0x7f71bac79c88

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 << " " ;

}

print(list);
-1 1 2 
Out[12]:
(void) @0x7f71bac79c88
void print(){

data<T>* tmp = first;

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

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 [14]:
list.pop_back();
print(list);
std::cout << std::endl;
mylist.pop_back();
mylist.print();
-1 1 
-1 1
Out[14]:
(void) @0x7f71bac79c88

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 [15]:
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[15]:
(void) @0x7f71bac79c88

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;
    }

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 [16]:
mylist.remove(2.0);
mylist.print();
1 40 -1 1
Out[16]:
(void) @0x7f71bac79c88

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 [17]:
#include <algorithm>
Out[17]:

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

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

p = std::find(list.begin(),list.end(),1.0);

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 was not found!
Element 1 was found!
0
1
Out[18]:
(std::basic_ostream<char, std::char_traits<char> >::__ostream_type &) @0x7f71cc841500