Skip to content

Bitonic Sorting Network

Sort a fixed size sequence with a bitonic compare and exchange network whose schedule is independent of input values.

A bitonic sorting network is a fixed sequence of compare and exchange operations that sorts a fixed number of elements. It is based on the idea of repeatedly building bitonic sequences and merging them.

A bitonic sequence increases and then decreases, or can be rotated into that shape. The network first creates bitonic sequences of small size, then merges them into larger sorted sequences until the full input is sorted.

Problem

Given nn input values, where nn is usually a power of two, sort them in nondecreasing order using a fixed comparator network.

Unlike quicksort or merge sort, the comparison schedule does not depend on the input values.

Algorithm

The iterative network form uses nested stages.

bitonic_sorting_network(A):
    n = length(A)

    for size = 2; size <= n; size *= 2:
        for stride = size / 2; stride > 0; stride /= 2:
            for i from 0 to n - 1:
                j = i xor stride

                if j > i:
                    ascending = (i & size) == 0

                    if ascending and A[i] > A[j]:
                        swap A[i], A[j]

                    if not ascending and A[i] < A[j]:
                        swap A[i], A[j]

The partner index is:

j=istride j = i \oplus stride

The value of stride decides which wires are compared in the current stage.

Comparator

Each comparator sorts a pair of positions.

compare_exchange(A, i, j, ascending):
    if ascending:
        if A[i] > A[j]:
            swap A[i], A[j]
    else:
        if A[i] < A[j]:
            swap A[i], A[j]

In a hardware or SIMD implementation, all independent comparators in the same stage can run at the same time.

Example

For n=4n = 4, the network performs these stages:

compare (0, 1) ascending
compare (2, 3) descending

compare (0, 2) ascending
compare (1, 3) ascending

compare (0, 1) ascending
compare (2, 3) ascending

Starting with:

[4,1,3,2] [4, 1, 3, 2]

The final output is:

[1,2,3,4] [1, 2, 3, 4]

Complexity

measurevalue
comparatorsO(nlog2n)O(n \log^2 n)
network depthO(log2n)O(\log^2 n)
extra spaceO(1)O(1)
input dependencenone

The same comparator pattern runs for sorted, reversed, random, and duplicate heavy inputs.

Correctness

The network builds sorted sequences by repeatedly applying bitonic merge. A bitonic merge takes a bitonic sequence and turns it into a sorted sequence by first comparing elements separated by half the sequence length, then recursively merging both halves.

The construction ensures that each larger stage receives valid bitonic input. By induction over sequence size, every stage produces correctly sorted subsequences. At the final stage, the full sequence is sorted.

Practical Considerations

  • It maps well to hardware, SIMD, and GPUs.
  • It has predictable control flow.
  • It performs more comparisons than many adaptive or comparison based sorts.
  • It works best for power of two lengths.
  • Non power of two inputs can be padded with sentinel values.
  • It is often used as a local sorting primitive rather than a full CPU array sort.

When to Use

Use a bitonic sorting network when:

  • input size is fixed
  • predictable execution matters
  • parallel compare and exchange is cheap
  • SIMD, GPU, FPGA, or hardware sorting is targeted
  • branches and input dependent control flow should be avoided

For general CPU sorting of large arrays, introsort, merge sort, radix sort, or sample sort usually performs less work.

Implementation

func BitonicSortingNetwork(a []int) {
	n := len(a)

	for size := 2; size <= n; size <<= 1 {
		for stride := size >> 1; stride > 0; stride >>= 1 {
			for i := 0; i < n; i++ {
				j := i ^ stride

				if j > i {
					ascending := (i & size) == 0

					if ascending && a[i] > a[j] {
						a[i], a[j] = a[j], a[i]
					}

					if !ascending && a[i] < a[j] {
						a[i], a[j] = a[j], a[i]
					}
				}
			}
		}
	}
}
def bitonic_sorting_network(a):
    n = len(a)

    size = 2
    while size <= n:
        stride = size // 2

        while stride > 0:
            for i in range(n):
                j = i ^ stride

                if j > i:
                    ascending = (i & size) == 0

                    if ascending and a[i] > a[j]:
                        a[i], a[j] = a[j], a[i]

                    if not ascending and a[i] < a[j]:
                        a[i], a[j] = a[j], a[i]

            stride //= 2

        size *= 2