Skip to main content

Section 27.8 Indexing and Accessing Elements

Vectors can access a particular element via its index efficiently because they store their elements in a contiguous block of memory. This allows them to do some simple arithmetic to calculate the address of any element based on its index.
In a linked list however, the elements are scattered throughout memory, so there is no easy way to calculate the address of a particular element based on its position in the list. Thus, in a linked list, accessing an element by index requires traversing the list from the beginning and following next pointers until we reach the desired index.

Activity 27.8.1. Linked List At.

The animation is set to auto step. Click Make current pointer and let it finish running. You will be pointing at index 0. Then click Advance current and let it run. Then click Advance current again and let it run. You should have a current pointer pointing at index 2 (the 8 node).

Instructions.

When the > button is highlighted, an animation is prepared to run. Use the Step button to step through the animation one step at a time.
When no animation is in progress, you can use the data controls to start a new animation.
You can click and drag on the animation area to pan around. Use the Ctrl + mouse wheel (or touchpad scroll gesture) while over the animation area to zoom in and out.
  • Core Controls.
    • << : Skip to the beginning of the animation.
    • < : Step backwards one step in the animation.
    • > : Step forward one step in the animation.
    • >> : Skip to the end of the animation.
    • Auto Step Speed Set to anything other than off to automatically step through the animation at the selected speed.
    • Zoom Set the zoom level. You can also use Ctrl + Mouse Wheel to zoom in and out.
  • Data Controls.
    • Value Use this area to enter a value when inserting, finding, deleting, etc...
To automate this in a retrieveAt function for our linked list, we can use a for loop that advances a current as many times as the desired index. After the loop finishes, our pointer will be pointing at the desired node, and we can return its data.
Listing 27.8.1. SimpleLinkedList retrieveAt Implementation (No bounds checking)
template <typename T>
T SimpleLinkedList<T>::retrieveAt(int index) const {
    ListNode<T>* current = head;
    for(int i = 0; i < index; i++) {
        current = current->next;
    }
    return current->data;
}

Note 27.8.1.

To make this function behave like the at function in std::vector, we would need to return a reference to the data instead of a copy, allowing for modification of the element directly.
But that would require the function to not be marked as const, since it would be modifying the list. Then, to support using .at() in both const and non-const contexts, we would need to provide two versions of the function: one const and one non-const.
We will avoid that complexity. If decide we there should be a way to change data in a node we could instead provide a separate function specifically for that purpose (something like .setAt(int index, constT& value)).
As long as our retrieveAt function is given a valid index, that logic will be correct. But what if the index is negative? That is clearly invalid. Or, what if the function is given an index that is larger than the number of elements in the list? In that case, the for loop will eventually try to do current = current->next; when current is nullptr, which will cause an error.
To handle those cases, we can add some checks. First, we can check if the index is negative at the start of the function and throw an exception if it is. We also should make sure that the list is not empty. If it is, any index is invalid.Then, inside the for loop, after moving current to the next node, we can check if current is nullptr. If it is, that means the index is out of range, and we can throw an exception in that case as well.
Listing 27.8.2. SimpleLinkedList retrieveAt Implementation (With bounds checking)
template <typename T>
T SimpleLinkedList<T>::retrieveAt(int index) const {
    if (index < 0 || head == nullptr) {
        throw std::out_of_range("Index out of range");
    }
    ListNode<T>* current = head;
    for(int i = 0; i < index; i++) {
        current = current->next;
        if (current == nullptr) {
            throw std::out_of_range("Index out of range");
        }
    }
    return current->data;
}
Having to keep checking β€œdid we go too far” (line 9) is a bit clunky. If we knew in advance how many elements were in the list, we could just check the index against that at the start of the function and avoid having to check inside the loop. (We will examine that possibility in a later section.)
Examining this function, we can see that in a list of \(n\) elements, this operation has a worst-case time complexity of \(O(n)\text{.}\) If we call .retrieveAt(n - 1), the loop will repeat \(n - 1\) times. This is much slower than doing the same operation on a std::vector, which has a time complexity of \(O(1)\text{.}\)
For this reason, although we generally like reusing existing logic to implement new functions, we will not want to use .retrieveAt() as a helper for other linked list functions. Imagine we implement a print function like this:
// Inefficient print function using retrieveAt()
template <typename T>
void SimpleLinkedList<T>::print() const {
    int listSize = this->listSize();
    for (int i = 0; i < listSize; i++) {
        cout << this->retrieveAt(i) << " "; 
    }
    cout << endl;
}
A call to .print() will result in an \(O(n^2)\) operation because we have a loop that runs \(n\) times, and each time through the loop we call .retrieveAt(i), which is an \(O(n)\) operation. Instead, we want to implement any functions that need to visit each element in the list by traversing the nodes directly, without using .retrieveAt().

Warning 27.8.2.

Remember: we never want to use .retrieveAt() as a helper in linked list logic.

Checkpoint 27.8.1.

Design the logic to print a range of elements [a, b] in a linked list. You may assume a and b are valid indices and a is less than or equal to b.
You have attempted of activities on this page.