Chapters

Hide chapters

Data Structures & Algorithms in Swift

Fourth Edition · iOS 15 · Swift 5.5 · Xcode 13

26. O(n²) Sorting Algorithms
Written by Kelvin Lau

Heads up... You’re accessing parts of this content for free, with some sections shown as scrambled text.

Heads up... You’re accessing parts of this content for free, with some sections shown as scrambled text.

Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.

Unlock now

O() time complexity is not great performance, but the sorting algorithms in this category are easy to understand and useful in some scenarios. These algorithms are space-efficient; they only require constant O(1) additional memory space. For small data sets, these sorts compare very favorably against more complex sorts.

In this chapter, you’ll be looking at the following sorting algorithms:

  • Bubble sort
  • Selection sort
  • Insertion sort

All of these are comparison-based sorting methods. They rely on a comparison method, such as the less-than operator, to order the elements. The number of times this comparison gets called is how you can measure a sorting technique’s general performance.

Bubble sort

One of the most straightforward sorts is the bubble sort, which repeatedly compares adjacent values and swaps them, if needed, to perform the sort. Therefore, the larger values in the set will “bubble up” to the end of the collection.

Example

Consider the following hand of cards:

5 5 94 08 4 6 8 0

3 3 6 0 3 5 61 94 4 3 6 4 1 1 77 84 5 6 2 4 6 3 03 54 7 2 6

Implementation

Open up the Swift playground for this chapter to get started. In the Sources directory of your playground, create a new file named BubbleSort.swift. Write the following inside the file:

public func bubbleSort<Element>(_ array: inout [Element])
    where Element: Comparable {
  // 1
  guard array.count >= 2 else {
    return
  }
  // 2
  for end in (1..<array.count).reversed() {
    var swapped = false
    // 3
    for current in 0..<end {
      if array[current] > array[current + 1] {
        array.swapAt(current, current + 1)
        swapped = true
      }
    }
    // 4
    if !swapped {
      return
    }
  }
}
example(of: "bubble sort") {
  var array = [9, 4, 10, 3]
  print("Original: \(array)")
  bubbleSort(&array)
  print("Bubble sorted: \(array)")
}
---Example of bubble sort---
Original: [9, 4, 10, 3]
Bubble sorted: [3, 4, 9, 10]

Selection sort

Selection sort follows the basic idea of bubble sort but improves this algorithm by reducing the number of swapAt operations. Selection sort will only swap at the end of each pass. You’ll see how that works in the following example and implementation.

Example

Assume you have the following hand of cards:

1 1 26 47 1 1 5 7

8 7 4 8 87 63 4 0 5 9 8 2 54 26 7 8 8 9 5 4 9 0 91 61 7 6 9

Implementation

In the Sources directory of your playground, create a new file named SelectionSort.swift. Write the following inside the file:

public func selectionSort<Element>(_ array: inout [Element])
    where Element: Comparable {
  guard array.count >= 2 else {
    return
  }
  // 1
  for current in 0..<(array.count - 1) {
    var lowest = current
    // 2
    for other in (current + 1)..<array.count {
      if array[lowest] > array[other] {
        lowest = other
      }
    }
    // 3
    if lowest != current {
      array.swapAt(lowest, current)
    }
  }
}
example(of: "selection sort") {
  var array = [9, 4, 10, 3]
  print("Original: \(array)")
  selectionSort(&array)
  print("Selection sorted: \(array)")
}
---Example of selection sort---
Original: [9, 4, 10, 3]
Selection sorted: [3, 4, 9, 10]

Insertion sort

Insertion sort is a more useful algorithm. Like bubble sort and selection sort, insertion sort has an average time complexity of O(), but the performance of insertion sort can vary. The more the data is already sorted, the less work it needs to do. Insertion sort has a best time complexity of O(n) if the data is already sorted. The Swift standard library sort algorithm uses a hybrid of sorting approaches, with insertion sort being used for small (<20 element) unsorted partitions.

Example

The idea of insertion sort is similar to how you’d sort a hand of cards. Consider the following hand:

8 8 13 65 5 8 2 2

2 6 0 4 20 51 8 8 9 5 5 3 42 77 1 3 7 2 1 3 62 43 3 7 0 7 9 9 1 3 0 5 4 05 73 4

Implementation

In the Sources directory of your playground, create a new file named InsertionSort.swift. Write the following inside the file:

public func insertionSort<Element>(_ array: inout [Element])
    where Element: Comparable {
  guard array.count >= 2 else {
    return
  }
  // 1
  for current in 1..<array.count {
    // 2
    for shifting in (1...current).reversed() {
      // 3
      if array[shifting] < array[shifting - 1] {
        array.swapAt(shifting, shifting - 1)
      } else {
        break
      }
    }
  }
}
example(of: "insertion sort") {
  var array = [9, 4, 10, 3]
  print("Original: \(array)")
  insertionSort(&array)
  print("Insertion sorted: \(array)")
}
---Example of insertion sort---
Original: [9, 4, 10, 3]
Insertion sorted: [3, 4, 9, 10]

Generalization

In this section, you’ll generalize these sorting algorithms for collection types other than Array. Exactly which collection types, though, depends on the algorithm:

public func bubbleSort<T>(_ collection: inout T)
    where T: MutableCollection, T.Element: Comparable {
  guard collection.count >= 2 else {
      return
  }
  for end in collection.indices.reversed() {
    var swapped = false
    var current = collection.startIndex
    while current < end {
      let next = collection.index(after: current)
      if collection[current] > collection[next] {
        collection.swapAt(current, next)
        swapped = true
      }
      current = next
    }
    if !swapped {
      return
    }
  }
}
public func selectionSort<T>(_ collection: inout T)
    where T: MutableCollection, T.Element: Comparable {
  guard collection.count >= 2 else {
    return
  }
  for current in collection.indices {
    var lowest = current
    var other = collection.index(after: current)
    while other < collection.endIndex {
      if collection[lowest] > collection[other] {
        lowest = other
      }
      other = collection.index(after: other)
    }
    if lowest != current {
      collection.swapAt(lowest, current)
    }
  }
}
public func insertionSort<T>(_ collection: inout T)
    where T: BidirectionalCollection & MutableCollection,
          T.Element: Comparable {
  guard collection.count >= 2 else {
    return
  }
  for current in collection.indices {
    var shifting = current
    while shifting > collection.startIndex {
      let previous = collection.index(before: shifting)
      if collection[shifting] < collection[previous] {
        collection.swapAt(shifting, previous)
      } else {
        break
      }
      shifting = previous
    }
  }
}

Key points

  • n² algorithms often have a terrible reputation. Still, some of these algorithms usually have some redeeming points. Insertion sort can sort in O(n) time if the collection is already in sorted order and gradually scales down to O().
  • Insertion sort is one of the best sorts in situations wherein you know that your data is mostly in sorted order ahead of time.
Have a technical question? Want to report a bug? You can ask questions and report bugs to the book authors in our official book forum here.
© 2024 Kodeco Inc.

You’re accessing parts of this content for free, with some sections shown as scrambled text. Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.

Unlock now