Abstract data type 2026-10-13
An abstract data type specifies values and operations independently of their storage. A FIFO queue has ordered sequences as its abstract values; an array and a linked list can represent those same values. A representation invariant identifies valid storage states, and an abstraction function says what each valid state means.[ref]
Adjacency list Created 2026-10-13 Updated 2026-10-20
An adjacency list represents a graph by keeping, for each vertex , a collection of the vertices reached by its outgoing edges. An array indexed by vertex identifiers can locate each collection; the collection itself may use a linked list or an array. In an undirected graph, each nonloop edge supplies an entry at both endpoints.[ref]
For a worked example, take the simple graph
where denotes the unordered pair . Its adjacency lists, displayed in label order, are
Thus has three neighbours, although the graph has six vertices. Its list contains exactly the destinations available for a single step from ; no slots are reserved there for . The fourteen neighbour entries represent seven edges, with appearing as in 's list and as in 's list. The label order makes the example easy to read; an adjacency list need not be sorted.
Figure 1.
Seven undirected edges and their fourteen adjacency list entries
. Details.
The useful idea is to store existing connections explicitly. Neighbour enumeration can then follow exactly the possible next steps, which is what breadth-first search and depth-first search need. In a sparse graph, most pairs of vertices have no edge; adjacency lists avoid reserving a separate adjacency matrix entry for every such absence. Whether this saves work depends on the question: neighbour enumeration and testing one proposed edge have different costs.
Write and . Suppose each vertex identifier, list descriptor, link and optional edge weight occupies a constant number of machine words, and indexed array access and identifier comparison take constant time. There are list descriptors and directed, or undirected, neighbour entries. Hence adjacency lists use machine words in Big O notation. A full adjacency matrix has entries, regardless of . These are counts under a storage model, not fixed byte sizes.[ref]
Our undirected example has six list descriptors and fourteen neighbour entries, versus thirty-six full adjacency matrix entries. The numbers and count different kinds of logical items: they do not prove a particular byte saving. A linked list also needs links; an array may have unused capacity. Storing only half of a symmetric matrix changes its exact count while leaving quadratic growth. If identifiers or weights require growing numbers of machine words, their size must be included in the space complexity.
Figure 1.
Adjacency matrix storage and queries compared with adjacency lists on the same six-vertex graph
. Details.
An unsorted adjacency list answers "does the edge exist?" by linear search through . In the worst case it inspects all entries, giving time, or for . Even if is absent, an uninspected entry could still contain it, so arbitrary lists require the whole scan. On our example, looking for in 's list compares with and fails after three comparisons. A full adjacency matrix instead answers from its single entry in time.[ref]
Different promises change the query. A sorted array of neighbour identifiers permits binary search in time, but maintaining order after insertion may require shifting entries. A per-vertex hash table permits expected constant-time membership under the hashing and load assumptions of that data structure; it does not give that guarantee to an arbitrary adjacency list. A stored list length can likewise make a vertex degree query constant-time even though neighbour enumeration still has to visit its entries.
The advantage grows with scale. For a graph path on vertices, , so there are undirected neighbour entries and descriptors, versus full adjacency matrix entries. Such a family is a sparse graph with . For a complete graph, , so the undirected neighbour entries already number : the adjacency list advantage in growth rate disappears. Choose by the workload: following existing edges favours adjacency lists; many arbitrary pair-membership queries can favour a full adjacency matrix. The representation organizes the same graph for different questions.
Array (data structure) 2026-10-13
A data structure storing entries at numbered positions, usually starting at index zero. The fixed-capacity representation considered here puts equal-sized slots consecutively in computer memory. The number of occupied slots can be smaller than the capacity; unused slots provide room for later entries. An array of references stores consecutive references, while the objects they identify can be elsewhere. [ref]
Let the first slot have abstract address and each slot occupy address units. Then indexed access computes
For toy values , , the slots for start at ; index goes directly to through . These are illustrative slot addresses, not observed allocation addresses. Under the word-RAM (random-access machine) model for time complexity, word-sized arithmetic operations and any addressed computer memory access have constant cost, and the addresses and entries fit in words. Thus one array access takes time in Big O notation, independently of the index. This is the access assumption needed by binary search. [ref]
Inserting after in an array holding with one unused slot produces : first copy from slot to slot , then write into slot . Writing first would lose unless it had been saved separately. More generally, insert at index by copying occupied slots from right to left, then fill the gap; with spare capacity this moves entries and takes time in Big O notation, so insertion near the middle has time complexity. Removing an entry closes the gap by shifting later entries left. [ref]
A fixed-capacity array cannot accept another entry when full. A resizable representation can allocate a larger array and copy the entries. Doubling capacity when full gives append cost by amortized analysis over a sequence starting empty, but a single append that copies entries still has worst-case time complexity. The outline is a geometric series: copied capacities sum to , less than twice the number of appends that triggered the last copy. Middle insertion still pays for its shifts. [ref]
Circular linked list 2026-10-24
A linked list in which following successors returns to the starting node rather than reaching null. A common non-dummy representation uses a null tail pointer for the empty case and a finite simple cycle for the nonempty case; tail.next is the first node. A traversal stops after one complete turn, comparing object identity with its remembered start. Appending is constant-time once the insertion point and absence of duplicate keys are established.
Doubly linked list 2026-10-24
A linked list whose nodes store predecessor and successor references satisfying reciprocal-link consistency. A known node can be unlinked in constant pointer work without searching for its predecessor. The representation uses extra references and requires both directions to be updated on insertion and deletion.
FIFO queue 2026-10-13
A FIFO queue is a data structure with first-in, first-out behavior: enqueue inserts at the rear, dequeue removes and returns the front, and peek inspects the front without removing it. Removal and inspection require a nonempty FIFO queue, or an explicitly defined empty-case result. Enqueue in that order, dequeue , then enqueue : the remaining sequence is , with next. A LIFO stack would remove the most recently inserted item instead. A FIFO queue models arrival-order service and supplies the next vertex for breadth-first search; a stochastic service model belongs to queueing theory. [ref]
The FIFO queue interface specifies removal order, not time complexity. A linked list with both front and rear pointers supports enqueue, dequeue and peek in worst-case time under constant-cost pointer operations and allocation, as in Sedgewick and Wayne's implementation. A plain array that deletes its first entry by shifting the remaining entries takes time per such removal. An array with advancing front and rear indices can avoid these shifts; storage and resizing policies still need analysis. The two-list functional queue gives a different implementation with constant amortized analysis cost along a single sequence of uses. These representation choices matter when deriving the linear time complexity of breadth-first search in Big O notation.
// Destination: computer-science.bigb
Hash collision Created 2026-10-13 Updated 2026-10-31
A hash collision is a function collision of a hash function : two distinct inputs have the same output, . Repeating the same input is not a hash collision. Unequal hash values prove the keys differ; equal hash values only identify candidates that might be equal. This makes a hash function useful as a compact filter before comparing the original data.
Here is a deliberately simple example using modular arithmetic. Let for nonnegative integer keys, and let a four-bucket hash table use the further bucket index . For keys , the hash values are , while all three bucket indices are . Thus and form a hash collision for ; and share a table bucket even though their full hash values differ. Always specify which function is being compared: a table collision concerns the bucket-index function, which may discard more information than the original hash function.
Figure 1.
A shared hash value and a shared table bucket discard different distinctions
. Constructed integer keys pass through and . Orange highlights the hash collision between and ; blue key joins them only after bucket reduction. Separate chaining retains all three keys. This is a toy calculation, not a measured workload or a cryptographic hash function. Details.
With separate chaining, bucket holds all three records in a linked list. A search for visits that bucket and compares the stored keys until it finds ; a search for visits the same bucket but rejects every stored key. A hash collision changes the search work, while key comparison preserves correctness. Storing only one record per hash value would incorrectly merge distinct keys. This is the purpose of collision handling in a hash table.[ref]
The pigeonhole principle explains inevitability: if a hash function has only possible outputs, any distinct inputs contain a hash collision. In particular, a function whose domain of a function is larger than its finite codomain cannot be an injective function. This is an existence statement; it gives neither an efficient way to find the pair nor a probability for a smaller collection of keys. A particular small collection might have no hash collision at all.
For a cryptographic hash function, collision resistance asks that finding any two distinct messages with equal full digests be computationally infeasible under the specified model and resources. It does not mean that hash collisions do not exist. Preimage resistance asks a different question: given a target digest , how hard is it to find a message with ? The collision finder may choose both messages and their common digest; the preimage finder is given the digest in advance. These definitions distinguish two search tasks, independently of how a hash table stores records.[ref]
Hash function 2026-10-13
A hash function is a function assigning each key in an input set a value in an output set . Usually is much smaller: a long name or large integer becomes a small bucket index. Equal keys must receive equal values from the same hash function; different keys may also receive equal values. Such a hash collision means "look in the same place", and does not establish key equality. If , the pigeonhole principle makes some collisions unavoidable.
For example, modular arithmetic gives the hash function , with seven bucket indices . Store the seven keys : their values are respectively . In particular, although . A hash table is a data structure that uses these indices to locate records. With separate chaining, each bucket holds a linked list of records, so bucket retains all four keys . Inserting must not overwrite merely because their hash function values agree.
Figure 1.
Seven keys stored in seven buckets using the hash function h(k) = k mod 7
. Orange arrows identify four colliding keys. Bucket 3 keeps all four records in a linked list; equality is checked on the stored keys. Details.
Searching for computes bucket , then compares the actual keys until it reaches . Searching for absent key reaches that same bucket and must reject every stored key. This data structure trades a search through all records for a search through one short chain, provided the hash function distributes keys adequately. A fixed hash function can perform poorly on patterned keys: already demonstrate the problem. A universal hash family introduces controlled randomness to limit the expected value of this extra work. Neither indexing nor a small collision probability inherently supplies encryption or message authentication; a cryptographic hash function has additional requirements such as collision resistance.
Reverse a finite acyclic linked list using a reversed-prefix head and an untouched-suffix head. Save the suffix head's old successor, redirect its successor to the prefix, then advance both heads. If and are the current sequences of node identities and the original sequence, the loop invariant is , with the two reachable node sets disjoint and all payload fields unchanged. Moving from to preserves this equation. The suffix length decreases, and at exit the prefix is exactly the reversed original list. The method takes linear running time and constant auxiliary space, reusing the original nodes.
Lazy list 2026-10-13
A linked list whose future structure is obtained by calling a delayed computation. In Standard ML, unit -> node delays a computation until demand. Filtering may need to force arbitrarily many rejected elements to produce its next retained element; filtering an infinite list need not produce a result. Delay alone does not imply memoization.
// Destination: computer-science.bigb
Linear search Created 2026-10-13 Updated 2026-10-14
Linear search, also called sequential search, examines a finite sequence from its beginning, performing an equality test on each entry until one matches the target or the entries run out. It needs no sorted array or total order on the values. This makes it useful for a small collection, a one-off lookup, or data available only in succession: the next entry is enough to make progress.
Specify the answer precisely. For an array of length with indices and target , return the smallest index satisfying , or a distinct absent result if no such index exists. Repeated values are allowed. In this contract, finding a later duplicate is insufficient when an earlier match exists. Assume the input remains unchanged during the search and each equality test terminates with a stable truth value expressing the intended matching relation.
Here is an implementation in Python; None represents absence and is distinct from the valid index zero:
def linear_search(entries, target):
    for index, entry in enumerate(entries):
        if entry == target:
            return index
    return None
The official Python sequence method s.index(x) likewise specifies the first occurrence, but signals absence by raising ValueError. An API's absent-result convention belongs to its contract.[ref]
Take the original unsorted array . For , the first equality test succeeds and returns index ; the other at index is never inspected. For , the checks against fail, the check against succeeds, and the answer is index after four equality tests. For absent target , all five equality tests fail and the answer is None. On the empty sequence, there is no entry to inspect: return None with zero equality tests.
Figure 1.
Linear search on an unsorted array: first duplicate, later match, and absent target
. Indices start at zero. Each numbered cell is one equality test; green cells match, orange cells fail, and grey cells are never inspected. The empty sequence requires zero equality tests. Details.
Value order never justifies skipping an entry in this algorithm. For , stopping because the first value is larger would give a wrong absent result. Any permutation of the input remains searchable by linear search, although the first matching index and number of equality tests can change. By contrast, binary search uses a total order and the sorted array property to discard whole candidate ranges. Its logarithmic time complexity additionally assumes cheap access to middle entries; a sorted linked list does not acquire constant-cost indexed access merely by being sorted. Sorting for a single query also has a cost, and changes positions relative to the original sequence.
Count just the equality tests between the target and an entry, excluding loop conditions and index updates. If the first match is at index , linear search uses exactly equality tests. If the target is absent, it uses exactly . Thus for the best successful case costs one equality test, and the worst case costs , attained by absence or a first match at the last index. For , the sole outcome is absence with .
Figure 1. . For length , a first match at index costs equality tests; absence costs eight. The dashed mean assumes a successful unique target uniformly located over the eight positions. It does not include absent queries. Details.
An expected value needs a specified probability distribution. For , condition on success, assume the target occurs exactly once, and assume its position has a uniform distribution on a finite set of indices . Then the expected value of the linear search comparison count is
This is the average of the costs under those assumptions, with no assumption that the array is sorted. It is not a universal average for arbitrary queries. For example, with , a unique successful target at index with probability and at index with probability has expected value , rather than .
Duplicates also require care about what is sampled. Choose a position uniformly in , then search for its value. All queries succeed, but the five possible sampled positions give costs : both positions holding cause an immediate match at index . The expected value is , not . Uniformly choosing an occurrence does not uniformly locate the first match.
More generally, let the success probability be and let be the conditional probability that the first matching index is , allowing duplicates. Since , the expected value is
Under the unique, uniformly located successful-target assumptions this becomes . If every query is absent, and without needing conditional probabilities. When , success is impossible and ; the successful-search conditional formula is undefined.
Converting the linear search comparison count to execution time needs a cost model. Under constant-cost equality tests, next-entry access, and loop bookkeeping, worst-case time complexity is in Big O notation for , with constant overhead even for the empty sequence. Variable-length entries can make an equality test expensive. On a linked list, advance along its links; repeatedly restarting at the head to obtain the next numeric index adds work that the comparison count does not capture. These are exact operation counts and conditional mathematical averages, not runtime measurements.
Linked list 2026-10-13
A data structure representing a sequence through linked list nodes and links between them. In the singly linked representation used here, each linked list node holds an item and a next reference to its successor. head identifies the first linked list node; the final next is null, meaning no successor. An empty linked list has head = null. Sedgewick and Wayne use this representation to implement both a stack and a FIFO queue. [ref]
For , place the linked list nodes at conceptual computer memory positions , respectively. These deliberately separated labels describe a toy layout, not actual pointer values. The sequence order comes from the links, not increasing positions. To read index , begin at head and follow two next references. For index , this takes link-following steps and time complexity, hence in the worst case for linked list nodes, under the constant-cost operations assumed for the array example. [ref]
Suppose a reference to the existing linked list node is already known. To insert a fresh linked list node after it, first set X.next = B.next, preserving the old successor , and then set B.next = X. The result is ; none of the old items moves. Reversing these assignments would read the newly changed B.next and set X.next = X, losing the route to from head. This is a two-update algorithm correctness argument, rather than a claim that finding the insertion location is free.
The two pointer updates take time in Big O notation. The complete insertion has time complexity if the predecessor is already known, allocating and initializing the fixed-size linked list node costs , and copying its item has constant cost. Finding from head can instead cost ; a position described only by an index does not confer the constant-cost advantage. This makes a linked list useful when an algorithm already holds the needed linked list node, or repeatedly works at the front as a stack does.
Deletion similarly needs the incoming link: to remove after known , set B.next = X.next. A reference to alone does not identify its predecessor in this singly linked representation. The head is a special case: after checking for a nonempty linked list, remove it with head = head.next. If a tail reference is also maintained, deleting the only linked list node must clear that reference too; a stored length must also be updated. Removing a linked list node from the sequence and reclaiming its storage are separate responsibilities. [ref]
There are practical costs beyond Big O notation. Each singly linked linked list node needs a successor reference in addition to its item; separate allocation may add further overhead, while an array can reserve unused capacity. Consecutive array slots can make a scan reuse nearby contents already brought into a memory cache; separated linked list nodes can make link following wait for successive computer memory fetches. These are layout-dependent effects, not a guarantee that every array operation beats every linked list operation on every machine. An array of references can still lead to scattered objects, and linked list nodes can be allocated close together. [ref]
Scan a nonempty linked list while remembering the predecessor of its least-valued node. A sentinel node supplies a predecessor even when the minimum is the first data node. Once the scan ends, bypass the selected node and detach its outgoing link. Using strict comparison retains the first occurrence among tied minima. Repeated extraction and tail-appending gives stable selection sort without replacing the data nodes or collapsing equal values.
Linked list node Created 2026-10-13 Updated 2026-10-24
One record holding an item and the references that connect it into a linked list. In the singly linked examples, its next identifies the successor or is null. A linked list node's position in the sequence can change when links change, without moving the linked list node in computer memory.
// Target: computer-science.bigb
List (abstract data type) 2026-10-20
A list is an ordered collection in which entries may repeat. Removing duplicates would discard information about both multiplicity and, depending on the representation, order. A linked list and an array are different ways to implement list operations.
Merge algorithm Created 2026-10-13 Updated 2026-10-18
The merge algorithm combines two finite sorted sequences under the same total order, retaining every entry exactly once. Keep a position at the first unconsumed entry, or "head", of each input. While both heads exist, compare their keys, append the smaller head to the output, and advance only that head's position. On equality take the left head. Once either sequence is exhausted, append the other remainder without further key comparisons. Empty inputs therefore require no head comparisons. The MIT notes illustrate this two-head combination step.[ref]
For example, the merge algorithm applied to and returns :
Figure 1.
Five head comparisons in the merge algorithm
. Orange heads are chosen and arrows lead to the next output entry. The compared head pairs are (1,2), (4,2), (4,3), (4,8) and (7,8). The left sequence then runs out; append (8,9) without further key comparisons. Details.
The head pairs , , , and select respectively. After the fifth comparison the left sequence is empty, so is appended. Seven entries are written, but only five head comparisons occur: tests for exhaustion and appending the remainder are separate operations. This distinction makes the example's count smaller than the bound .
For nonempty inputs of lengths , each head comparison consumes exactly one entry. After such removals at most one entry remains, so both heads cannot still exist. Hence the merge algorithm uses at most head comparisons. The bound can be attained: and require six head comparisons before the left sequence is exhausted. With constant-cost comparisons, access and copying, writing all entries gives time complexity in Big O notation. Writing a separate output array needs entry slots; the two positions require only constant additional storage.
The merge algorithm is useful whenever two already ordered collections must become one: it reads each input only forwards. This works with arrays and linked lists, without needing access to arbitrary middle positions. Choosing left on ties preserves each input's internal order and places equal left entries before equal right entries, which is the combination rule needed for stable sorting in merge sort.
The loop invariant behind algorithm correctness is that the emitted prefix is sorted, contains exactly the consumed entries, and every emitted key is at most every unconsumed key. Each sorted sequence has its smallest remaining key at its head, so the smaller of the two heads is safe to append. The invariant survives each removal; when one sequence ends, the other sorted remainder is safe to append in full. Each comparison removes an entry, so termination follows from the finite input lengths.
// Target: computer-science.bigb
Merge sort Created 2026-10-13 Updated 2026-10-18
Merge sort is a divide-and-conquer algorithm that sorts a finite array by splitting it into contiguous halves, sorting each half using recursion, and combining the two sorted arrays with the merge algorithm. An empty or one-entry array is returned immediately. For length , put the first entries on the left and the remaining on the right; the brackets are the floor function and ceiling function. Always choose the left head on equal keys to obtain stable sorting. This is the split, sort, combine structure taught in the MIT lecture notes.[ref]
Take the concrete input array
The tags identify the entries originally at indices and , counting from zero. Both keys are ; the tags are never compared. Reading downward in the figure follows the splits, then the returned sorted arrays. Arrows show dependencies; the level layout groups independent calls rather than giving their chronological execution order.
Figure 1.
Merge sort splits into base cases and returns sorted arrays
. The tagged keys 2a and 2b retain their original order through stable sorting. The merge algorithm makes 4, 6 and 7 head comparisons on the three returning levels, for 17 in total. Details.
The first two halves are and . Their pairs split into one-entry base cases. Applying the merge algorithm gives the following fully worked return levels; each number in the last column counts only comparisons between two available heads.
Returning levelResulting sorted arraysHead comparisons
Pairs, , ,
Halves,
Whole array
In the left-half merge algorithm, the head pairs are , and ; it takes , then appends . In the final merge algorithm, the head pairs are , , , , , and . It takes , then appends . Thus this precise version makes head comparisons and, ignoring identity tags, returns .
The elegance of merge sort is that it creates exactly the structure its next step needs: two smaller sorted arrays make the larger ordering inexpensive. Once the whole array is sorted, equal keys are adjacent and binary search can serve repeated queries. Stable sorting also retains information that the keys alone do not express. If the left-half merge algorithm instead takes the right head on the tie, it returns ; the keys are sorted but stable sorting has failed.
For time complexity, assume each key comparison, entry access and entry copy has constant cost. The merge algorithm processes entries in time. In Big O notation, the resulting recurrence relation is
For , the balanced levels contain entries each and there are returning levels. Each costs , so the worst-case time complexity is . For arbitrary , there are at most returning levels, each processing at most entries, giving the same bound. The worst-case number of head comparisons is also . Here is the base-two logarithm. The MIT notes give this level-by-level analysis for merge sort.[ref]
The conventional array implementation uses a reusable buffer of entries for the merge algorithm, together with pending calls for recursion. Its auxiliary space complexity is therefore , beyond the original array. This bound describes that implementation. A linked list permits a merge algorithm that reconnects existing nodes; its storage accounting differs from copying array entries into a buffer.
For a short algorithm correctness outline, the empty and one-entry base cases are sorted. Assuming recursion correctly sorts both shorter halves, the merge algorithm returns all their entries in order. Equal keys within a half keep their relative order by the same argument applied to the smaller call; across halves, every left entry originally preceded every right entry, so taking left first preserves stable sorting. Both half-lengths decrease for , establishing termination.
Move-to-front heuristic Created 2026-10-24 Updated 2026-10-31
After a successful search in a linked list, detach the found linked list node and insert that same linked list node at the front. An immediate repetition of that search then succeeds after one key comparison. This helps workloads with temporal locality, though it need not reduce the cost of the search that triggers the move.
A dummy header node lets a search return the found node's predecessor. If that predecessor is already the header, do nothing; otherwise bypass the found node, point it at the old first real node, and point the header at it. This preserves each key/value binding and avoids allocation. It preserves a dictionary abstraction whose ordering is unobservable, but generally changes the abstraction of an ordered list.