Data Structure (1) - Lists, Stack & Queue

Lists

Linked List

// List.h
#pragma once

template <typename T>
class List {
    public:
    //
    
    private:
        class ListNode {
            public:
                T data; // a place to store data
                ListNode *next; // a pointer pointing to next node
                ListNode(T &data) : data(data), next(NULL);
                // a constructor that inits data with ref
                // and default next to NULL
        }
        
        ListNode *head_;
}




// List.cpp

#include "List.h"

template <typename T>
void List::insertAtFront(const T &d){// d: data
    ListNode *new_node = new ListNode(d);
    new_node->next = head_;
    head_ = new_node;
}


// returning a reference of a pointer to a ListNode inside List<T>
// return the reference of a pointer, so we can update the pointer
// typename on line 40 tells List<T>::ListNode is a type
template <typename T>
typename List<T>::ListNode *&
List<T>::_index(unsigned index) {
    ListNode *&_index_helper(ListNode *n, unsigned i) {
        if(i==0) {
            return n;
        } else {
            return _index_helper(ListNode n->next, i-1); // recursion
        }
    }
    
    return _index_help(_head, index); 
}


template <typename T>
T & List<T>::operator[] (unsigned index) {
    ListNode * node = _index(index);
    return node->data; // returns the reference of data
}
// so we can do things like: myList[1] = 2;


template <typename T>
void List<T>::insert(const T &t, unsigned index) {
    ListNode * &node = _index(index); // get original index 2
    // this is a reference to the pointer to original index 2 node
    // it is the reference of node 1's 'next'
    ListNode *new_node = new ListNode(t); // create new node
    new_node->next = node; // put original index 2 as this node's next
    node = new_node; // update original pointer from original index 2
    // to new node
}


template <typename T>
void List<T>::delete(unsigned index) {
    ListNode * &node = _index(index);
    ListNode * temp = node;
    node = node->next;
    delete temp;
}

Other Notes

  • O(1) insert/remove at front or back
  • O(1) insert/remove after a given element
  • O(n) lookup by index

Array List

#pragma once
template <typename T>
class List {
    public:
    private:
        T * data_; // an array T[]
        unsigned size_;
        unsigned capacity_;
}

When the array does not have enough empty space, it will need to expand (by creating a larger new array, copying existing items then delete the original array).

Resizing strategy

size n to size n+2: Amortized O(n) for inserting each item (copy cost)

size n to size 2n: Amortized O(1) for inserting each item but on average will have more space wasted.

std::vector

Standard implementation for array list in C++.

Other Notes

  • O(1) amortized insert/remove at front or back
  • O(n) insert/remove at any location (cost of shifting elements)
  • O(1) lookup by index

Stack & Queue

Queue

Order: FIFO (first in first out)

Implementation: On top of list with enqueue() to add and dequeue to remove.

Runtime: O(1) for both enqueue and dequeue.

Stack

Order: LIFO (last in first out)

Implementation: On top of list with push() to add, and pop() to remove.

Runtime: O(1) for both push and pop.

ref: https://courses.engr.illinois.edu/cs225/fa2020/