Embedded Linked Lists (Array-Lists)

Linked Lists vs. Arrays

We've seen how these two data structures differ in both their implementations and runtime complexities. Our allocator used fixed-sized blocks and used pointers to keep track of the next block on the free list. Suppose we want to "manage" our nodes in the array. Storing integers, a node would look something like this:
struct ArrayNode
{
  int prev_; // Previous "pointer"
  int data_; // Data
  int next_; // Next "pointer"
};
Our array would be an array of ArrayNodes instead of an array of integers

Of course, we'd want to have a class that implements this linked-list-array to have a full interface:

template <typename T>
class ArrayList
{
  private:
    struct ArrayNode
    {
      int prev_;
      T data_;
      int next_;
    };

  public:
    ArrayList(unsigned MaxElements);
    ~ArrayList();

      // Note: These 4 methods are all O(N)
    const T& operator[](unsigned index) const;
    T& operator[](unsigned index);
    void insert(const T& value, unsigned index);
    T remove_byindex(unsigned index);

    void push_front(const T& value);
    void push_back(const T& value);
    void pop_front();
    void pop_back();
    void clear();

    int find(const T& data) const;
    bool empty() const;
    unsigned size() const;
};
We would have some private members to represent an array of ArrayNodes:
private:
  int head_;
  int tail_;
  unsigned capacity_;
  ArrayNode *list_;
And in the constructor:
template <typename T>
ArrayList<T>::ArrayList(unsigned MaxElements)
{
  head_ = NULL_NODE;
  tail_ = NULL_NODE;
  capacity_ = MaxElements;
  list_ = new ArrayNode[capacity_];
}
Since 0 is a valid index, we need to choose another value to represent NULL. We also need a way to tell if a "node" is in use or not (EMPTY).
const int NULL_NODE = -1;  // 0 is a legal index, so -1 will be our NULL
const int EMPTY_NODE = -2; // Set a node's prev/next to this to indicate it's available.
A sample program run consisting of these statements: (The list shown is the logical ordering which is different from the physical order)
void TestArrayList()
{
  ArrayList<char> foo(8); // Assume we don't know what an ArrayList is.  
  
    // Push back 5 characters, ABCDE
    // A  B  C  D  E
  for (char c = 'A'; c < 'F'; c++)
    foo.push_back(c);

                          // Showing it as a linked list [or as an array]
  foo.insert('X', 0);     // X  A  B  C  D  E       [XABCDE]
  foo.remove_byindex(2);  // X  A  C  D  E            [XACDE]
  foo.remove_byindex(3);  // X  A  C  E                [XACE]
  foo.insert('Y', 2);     // X  A  Y  C  E            [XAYCE]
  foo.push_front('P');    // P  X  A  Y  C  E       [PXAYCE]
  foo.push_front('Q');    // Q  P  X  A  Y  C  E  [QPXAYCE]
  foo.pop_back();         // Q  P  X  A  Y  C       [QPXAYC]
  foo.pop_front();        // P  X  A  Y  C            [PXAYC]
  foo[3] = 'J';           // P  X  A  J  C            [PXAJC]
}
If you just saw this code below, you would have no idea what foo is or how it is implemented. And that's the point. It's an abstract data type. The implementation details do not need to be known to the clients.

foo.push_back('A');
foo.push_back('B');
foo.push_back('C');
foo.push_back('D');
foo.push_back('E');
foo.insert('X', 0);
foo.remove_byindex(2);
foo.remove_byindex(3);
foo.insert('Y', 2);
foo.push_front('P');
foo.push_front('Q');
foo.pop_back();
foo.pop_front();
foo[3] = 'J';
foo could be implemented as an array, or linked list, or dequeue, or many other structures.

Details

A node could be laid out like this: (the actual ordering is not important)

The list after a constructor call:

ArrayList<char> list(8);   (head=-1, tail=-1, -1 is the value of NULL_NODE)

Pushing 5 items:

for (char c = 'A'; c < 'F'; c++)
  list.push_back(c);
The head "pointer" doesn't move during the five calls to push_back. However, the tail "pointer" moves with each call. (head=0, tail=0), then (head=0, tail=1), then (head=0, tail=2), etc.

When the loop ends, head is 0 and tail is 4:

A
AB
ABC
ABCD
ABCDE


list.insert('X', 0);   (head=5, tail=4)
XABCDE


list.remove_byindex(2);   (head=5, tail=4)
XACDE


list.remove_byindex(3);   (head=5, tail=4)
XACE


list.insert('Y', 2);   (head=5, tail=4)
XAYCE


list.push_front('P');   (head=3, tail=4)
PXAYCE


list.push_front('Q');   (head=6, tail=4)
QPXAYCE


list.pop_back();   (head=6, tail=2)
QPXAYC


list.pop_front();   (head=3, tail=2)
PXAYC


list[3] = 'J';   (head=3, tail=2)
PXAJC

With arrows to show the "links":
PXAJC


Finding empty blocks?

A freelist:
PXAJC