Data Structures and Algorithms

Linked Lists and Iterators

Recap – Bounds Checking for Arrays Example

Basic Layout

class Array {
 public:
    Array(unsigned len = 0) : length(len) {
        data = (len ? new double[len] : nullptr);
    }
 private:
    double *data;        // array data
    unsigned int length; // array size
}

You could use a struct, but users of your class would be able to access all member variables of your struct by default.

Copy Constructor

To copy the array, you need to perform a deep copy:

Array(const Array & a) {
    length = a.getLength(); // getter for length // 1 step
    data = new double[length];                   // 1 step
    for (unsigned i = 0; i < length; i++) {      // n times
        data[i] = a[i];                          // c steps
    }
}

The complexity of this is \(O(1 + 1 + (nc) + 1) = O(n)\)

Better Copying

void copyFrom(const Array & a) {
    if (length != a.length) {
        delete[] data;
        length = a.length;
        data = new double[length];
    }

    for (unsigned int i = 0; i < length; i++) {
        data[i] = a.data[i];
    }
}

Array(const Array & a) : length(0), data(nullptr) {
    copyFrom(a);
}

Array & operator=(const Array & a) {
    copyFrom(a);
    return *this;
}

The Big 5

  • Destructor
  • Copy Constructor
  • Overloaded operator=
  • Copy constructor from r-value
  • Overloaded operator= from r-value

Destructor

~Array() {
    if (data != nullptr) {
        for (int i = 0; i < length; i++) { // n times
            delete data[i];                // 1 step
        }
        delete[] date;                     // 1 step
        data = nullptr;                    // 1 step
    }
}

Complexity: \(O(n)\)

operator[]

const double & operator[](int idx) const {
    if (idx < length && idx >= 0)
        return data[idx];
    throw runtime_error("bad idx");
}
  • Declares read-only access
  • Automatically selected by the compiler when an array being access is marked const
  • Helps compiler optimize code for speed
  • Some functions, like ostream &operator<<(ostream &os, const Array &a) require const.
  • Must also make a non-constr version

More than 2 dimensions

const double &operator()(int i, int j) const {
    // return by const reference
}
  • Can't overload operator[][]

Inserting an Element

bool insert(int index, double val) {
    if (index >= size || index < 0) {
        return false;
    } else {
        for (int i = size - 1; i > index; --i) {
            data[i] = data[i - 1];
        }
        data[index] = val;
        return true;
    }
}

Complexity:

  • Best case: \(O(1)\)
    • The element is inserted at the end
  • Average case: \(O(n)\)
    • Go through half of elements (but that is a constant times \(n\))
  • Worst case: \(O(n)\)
    • Go through all elements

Append

When array is full, resize

  • Double array size from n to 2n
  • Copy n items from the original array to the new array

  • Appending n elements

  • Total: 1 + n + n = 2n + 1 steps
  • Amortized: (2n + 1)/n = O(1) steps

Linked Lists and Iterators

  • Linked list: Each person points to the next person
  • Doubly linked list: Each person points to each other
  • Circularly linked list: A list which has the first and last nodes pointing to each other

Arrays vs Linked Lists

Arrays

Access
  • Random in O(1) time
  • Sequential in O(1) time
Insert
  • Inserts in O(n) time
  • Appends in O(n) time
Bookkeeping
  • ptr to beginning
  • current_size or ptr
  • max_size or ptr to end of allocated space
Memory
  • Wastes memory if size is too large
  • Requires reallocation if too small

Linked Lists

Access
  • Random in O(n) time
  • Sequential in O(1) time
Insert
  • Inserts in O(n) time
  • Appends in O(n) time
Bookkeeping
  • head_ptr to first node
  • size (optional
  • tail_ptr to last node
  • In each node, ptr to next node
  • Wasteful for small data items
Memory
  • Allocates memory as needed
  • Requires memory for pointers

Linked List Implementation

class LinkedList {
 public:
    LinkedList();
    ~LinkedList();
 private:
    struct Node {
        double item;
        Node *next;
        Node() {next = nullptr;}
    };

    Node *head_ptr;
};

Methods

int size() const;

bool append_item(double item);
bool append_node(Node *n);

bool delete_item(double item);
bool delete_node(Node *n);

Maintaining Consistency

Arrays
  • Stored size matches number of elements at all tiems
  • Be sure that start_ptr + size < end_ptr
Linked Lists
  • Stored size matches number of elements
  • The last node points to nullptr
  • If the list is a circuilar list, last node points to head
  • In a doubly-linked list, next/prev pointers are consistent