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.
Equality test 2026-10-14
A terminating check returning whether two values match under a specified relation. Mathematical equality is one such relation; a chosen equivalence relation can instead express matching keys. Linear search only needs this check, whereas binary search also uses relative order. The cost of an equality test depends on the representation and implementation of its operands.
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.
Use a linear search over every pair of array indices.
PROCEDURE SearchAll(x: INTEGER; VAR i, j: INTEGER): BOOLEAN;
VAR r, c: INTEGER;
BEGIN
  FOR r := 0 TO M-1 DO
    FOR c := 0 TO N-1 DO
      IF f[r,c] = x THEN i := r; j := c; RETURN TRUE END
    END
  END;
  RETURN FALSE
END SearchAll;
For a worst-case lower bound, consider an input in which every inspected element differs from . If an element remains uninspected, changing just that element to gives an indistinguishable execution requiring the opposite answer. Therefore a correct exact search algorithm must inspect all elements in the worst case.
The printed phrase “at least ” uses the upper-bound notation for a lower-bound claim. Its precise interpretation is
This is an adversary argument for access to arbitrary independent array entries.
Linear search examines successive entries until a match or a stopping condition is reached; in the worst case it inspects entries. Binary search uses a sorted array to discard about half of the remaining candidates after each comparison, requiring comparisons. Its logarithmic guarantee also requires constant-cost indexed access, as supplied by an array; the same index operations on a linked list would not be constant-cost.
Insert a singleton mapping, retrieve a stored representative, or relabel every occurrence of the second block's old representative:
def makeSet(x: Int): Unit = {
  require(isInMap(x) == -1, "element already present")
  require(used < capacity, "capacity exhausted")
  elements(used) = x
  representatives(used) = x
  used += 1
}

def find(x: Int): Int = {
  val i = isInMap(x)
  require(i >= 0, "unknown element")
  representatives(i)
}

def union(x: Int, y: Int): Unit = {
  val keep = find(x)
  val replace = find(y)
  if (keep != replace) {
    var i = 0
    while (i < used) {
      if (representatives(i) == replace) representatives(i) = keep
      i += 1
    }
  }
}
For makeSet, the new key is absent, its representative is itself, and old mappings are unchanged. find is a read-only linear search. In union, snapshot both representatives before changing any cell: recomputing the representative of y during the scan could change the criterion halfway through. The loop invariant says that every processed index already has its specified final representative and every unprocessed index still has its old representative. The cached labels make this assertion stable.
At exit the abstract postcondition in part (a) holds. The surviving representative was already a fixed point and belongs to its old block; every member of the replaced block, including its old representative key, now maps to it. Thus idempotence and key uniqueness are preserved. Duplicate insertions, absent-element operations and capacity overflow fail before changing the map. Each operation is in the worst case, except that a same-block union can skip the relabel scan after its lookups. Storage is . These bounds follow from the chosen parallel-array quick-find representation; no parent-pointer optimization is being assumed.
Quick-find 2026-10-24
An eager representation of a disjoint-set data structure that stores each member's current representative directly. Finding a representative is direct when keys are array indices; arbitrary keys in a parallel key array can add a linear search. A disjoint-set union operation scans members and replaces every occurrence of one old representative with the other. The representative labels must be snapshotted before this scan. Its easy abstraction function contrasts with the parent-pointer representation of a disjoint-set forest.
Time complexity Created 2026-09-24 Updated 2026-10-14
The time complexity of an algorithm bounds its number of computation steps as a function of its input length.
Specify both the size parameter and the operations counted. For binary search, take to be the number of array entries and count a three-way comparison of the target with one entry as one operation. Code using separate equality and less-than tests may perform two primitive tests per inspected entry; this changes a constant factor. The input size in bits is a different parameter when entries have variable-length representations.
Worst-case time complexity maximizes the count over permitted inputs of size . An absent target forces comparisons in linear search. For binary search, the worst-case count is for , attained by a target larger than every entry. Both have a best case of one comparison when the first inspected entry matches.
In Big O notation, the worst-case bounds are and respectively. Here means that fixed constants exist with for all . It is an upper bound, so a logarithmic count is also ; the sharper bound communicates its scaling better. The base of the logarithm changes only a constant factor.
For , linear search can require comparisons, whereas binary search needs at most , since . Doubling adds just one to the worst-case binary search count. These are operation counts, not measured machine runtimes. Translating them into logarithmic execution time assumes constant-cost indexed access, comparison and index arithmetic. Long entries can make comparison expensive; a linked list does not provide constant-cost access to an arbitrary middle index. Building or sorting the array is additional work: merge sort can provide order for many subsequent searches, but its cost belongs in the analysis too.