Elementary Data Structures: Stacks, Queues, Linked Lists, and Trees

Before tackling advanced data structures, mastering the elementary building blocks is essential, since nearly every complex structure is built from these fundamentals. This comprehensive guide covers array-based stacks and queues, singly and doubly linked lists, and the standard techniques for representing rooted trees, including the clever left-child right-sibling representation for trees with unbounded branching.

Stacks and QueuesLinked ListsTree Representation

~6 دقیقه مطالعه · آخرین به‌روزرسانی ۱۶ شهریور ۱۴۰۵

Why Elementary Structures Matter

Every advanced data structure covered later in this series — hash tables, balanced trees, graphs — is ultimately built from a small set of elementary structures: arrays, linked lists, and the abstractions layered on top of them. Understanding these fundamentals deeply, including their exact time complexity for each operation, is essential before moving to anything more sophisticated.

Stacks: Last-In, First-Out Access

A Stack is an abstract data type supporting insertion and deletion following a LIFO (Last-In, First-Out) policy: the most recently inserted element is always the first one removed. The two core operations are PUSH (insert) and POP (remove and return the most recent element).

STACK-EMPTY(S):
  return top == 0

PUSH(S, x):
  top = top + 1
  S[top] = x

POP(S):
  if STACK-EMPTY(S):
      error "underflow"
  else:
      top = top - 1
      return S[top + 1]

Implemented with an array and a simple top index, every stack operation runs in O(1) time. Stacks appear throughout computer science: function call management (the call stack), expression evaluation, undo functionality in software, and depth-first search traversal, covered later in this series.

Queues: First-In, First-Out Access

A Queue follows the opposite discipline, FIFO (First-In, First-Out): the earliest inserted element is the first one removed. The two core operations are ENQUEUE (insert at the tail) and DEQUEUE (remove from the head).

Implementing a queue efficiently with a fixed-size array requires a Circular Buffer approach, using separate head and tail indices that wrap around the array's end.

ENQUEUE(Q, x):
  Q[tail] = x
  if tail == Q.length:
      tail = 1
  else:
      tail = tail + 1

DEQUEUE(Q):
  x = Q[head]
  if head == Q.length:
      head = 1
  else:
      head = head + 1
  return x

Like stacks, both queue operations run in O(1) time. Queues are essential for breadth-first search, covered later in this series, task scheduling, and any scenario requiring processing in the exact order items arrive.

Linked Lists: Flexible, Pointer-Based Sequences

Unlike arrays, which require contiguous memory and have a fixed size, a Linked List stores elements in individually allocated nodes connected by pointers, allowing efficient insertion and deletion anywhere in the sequence without shifting other elements.

Singly Linked Lists

Each node in a Singly Linked List stores a value and a pointer to the next node, with the last node's pointer set to a special null value.

Structure of each node:
  key
  next  (pointer to the following node, or NIL if last)

LIST-SEARCH(L, k):
  x = L.head
  while x ≠ NIL and x.key ≠ k:
      x = x.next
  return x

Searching takes O(n) time in the worst case, since it may require traversing the entire list. Insertion at the head takes O(1) time, but insertion at an arbitrary position requires first finding that position, taking O(n) time overall unless a direct pointer to the target location is already available.

Doubly Linked Lists

A Doubly Linked List adds a second pointer to each node, referencing the previous node as well as the next one. This extra pointer makes deletion significantly more efficient when a pointer to the node itself is already available.

Structure of each node:
  key
  next  (pointer to the following node)
  prev  (pointer to the preceding node)

LIST-DELETE(L, x):
  if x.prev ≠ NIL:
      x.prev.next = x.next
  else:
      L.head = x.next
  if x.next ≠ NIL:
      x.next.prev = x.prev

Given a direct pointer to node x, deletion runs in O(1) time, since neither the previous nor next node needs to be located by searching — they are directly accessible through x's own pointers. This is a meaningful advantage over singly linked lists, where deleting a node requires first finding its predecessor by searching from the head.

A Practical Simplification: Sentinels

Many linked list implementations use a Sentinel, a dummy node that does not represent a real element but simplifies boundary conditions by eliminating special-case checks for the head and tail of the list. A circular, doubly linked list with a sentinel allows every insertion and deletion to use exactly the same code, without checking whether the list is empty or whether an operation affects the first or last real element.

Representing Rooted Trees

Trees require more sophisticated pointer structures than linear lists, since each node may have a variable number of children.

Binary Trees: The Simple Case

For a Binary Tree, where every node has at most two children, representation is straightforward: each node stores pointers to its left child, right child, and optionally its parent.

Structure of each node:
  key
  p       (pointer to parent, or NIL for the root)
  left    (pointer to left child, or NIL)
  right   (pointer to right child, or NIL)

This representation is used extensively for binary search trees and other binary tree structures covered later in this series.

Trees with Unbounded Branching: Left-Child, Right-Sibling

For a general tree, where a node might have any number of children, storing a separate pointer for every possible child is impractical, since the number of children is not known in advance and can vary widely between nodes. The elegant Left-Child, Right-Sibling representation solves this using only two pointers per node, regardless of how many children that node actually has.

Structure of each node:
  key
  p              (pointer to parent)
  left-child     (pointer to the node's first/leftmost child)
  right-sibling  (pointer to the node's next sibling to the right)

Each node points to only its leftmost child directly; to reach that child's siblings, the algorithm follows the leftmost child's right-sibling pointer repeatedly, effectively encoding an arbitrary-arity tree as a specific kind of binary tree.

Example: a node with three children A, B, C

Using left-child/right-sibling:
node.left-child → A
A.right-sibling → B
B.right-sibling → C
C.right-sibling → NIL (no more siblings)

To visit all children, start at node.left-child
and repeatedly follow right-sibling pointers

This representation elegantly handles trees of arbitrary and varying branching factor using a constant amount of memory per node, exactly two pointers, regardless of how many actual children each node has.

Why Mastering These Structures Matters

Every data structure discussed later in this series — hash tables using linked lists to handle collisions, binary search trees using the pointer-based node structure introduced here, and even graph representations using adjacency lists — builds directly on these elementary structures and their associated time complexities. A solid, intuitive grasp of exactly how and why each elementary operation achieves its stated running time is the foundation for reasoning correctly about every more advanced structure that follows.

نوشته و پژوهش‌شده توسط دکتر شاهین صیامی

مقالات مرتبط

Number-Theoretic Algorithms: GCD, Modular Exponentiation, and RSA

Modern cryptography and countless algorithmic applications rely on a handful of elegant number-theoretic algorithms. This comprehensive guide covers Euclid's algorithm for computing the greatest common divisor, fast modular exponentiation for efficiently computing large powers, and the mathematical foundation of RSA encryption, one of the most widely deployed cryptographic systems in the world.

ادامه

Computational Geometry Basics: Orientation, Line Intersection, and Convex Hull

Geometric algorithms solve problems involving points, lines, and shapes, appearing in computer graphics, robotics path planning, and geographic information systems. This comprehensive guide covers the cross-product-based orientation test that underlies nearly every geometric algorithm, segment intersection detection built on that test, and Graham's scan algorithm for computing the convex hull of a set of points.

ادامه

String Matching Algorithms: Naive Search, Rabin-Karp, and Beyond

Searching for a pattern within a larger text is one of the most common operations in computing, from text editors to DNA sequence analysis. This comprehensive guide covers the naive string-matching algorithm and its quadratic worst case, then explains the Rabin-Karp algorithm's clever use of hashing to achieve fast average-case performance, including how it handles hash collisions correctly.

ادامه

Approximation Algorithms: Getting Provably Close to Optimal for Hard Problems

When a problem is proven NP-complete, an exact efficient solution is unlikely to exist, but that does not mean giving up on the problem entirely. This comprehensive guide explains approximation algorithms, which sacrifice guaranteed optimality for guaranteed efficiency, covering the vertex cover and traveling salesman problems as classic examples with provable approximation ratios.

ادامه

NP-Completeness Explained: P, NP, and Why Some Problems Resist Efficient Solutions

Some problems have resisted every attempt at an efficient algorithm for decades, yet no one has proven an efficient solution is impossible. This comprehensive guide explains the classes P and NP, the concept of polynomial-time reductions used to compare problem difficulty, and how proving a problem NP-complete provides strong evidence, though not proof, that no efficient algorithm exists.

ادامه

Maximum Flow: Ford-Fulkerson and the Min-Cut Max-Flow Theorem

Maximum flow problems model the largest possible throughput through a network with capacity-limited connections, from water pipes to data networks. This comprehensive guide introduces flow networks, walks through the Ford-Fulkerson method for finding maximum flow using augmenting paths, and explains the elegant min-cut max-flow theorem that connects two seemingly different problems into one.

ادامه