Swift, Sorting and Binary Search Algorithm

Sorting algorithms and binary search implementation in Swift

Useful Foundation APIs

swapAt

  • swap the value in an array by passing the index

Bubble Sort

func bubbleSort(input: inout [Int]) {
    for i in 0..<input.count {
        for j in 1..<input.count {
            if input[j] < input[j - 1] {
                input.swapAt(j, j-1)
            }
        }
    }
}

var input = [4, 5, 2, 1, 6, 8, 9, 12, 13]
bubbleSort(input: &input)
screenshot 2024 03 10 at 9.39.01e280afpm
screenshot 2024 03 11 at 9.41.53e280afpm

Every steps compares 2 values and swapped it

Red = Sorted Item

Time complexity is O(n^2)

Space complexity is O(1)

Insertion Sort

Apple Foundation uses TimSort (InsertionSort + MergeSort)

screenshot 2024 05 15 at 9.22.59e280afpm
screenshot 2024 05 15 at 9.24.43e280afpm

Insertion Sort is another O(N^2) quadratic running time algorithm

On large datasets it is very inefficient – but on arrays with 10-20 items it is quite good. (Apple uses TimSort, It items is less than 64 then use Insertion Sort. -> https://github.com/apple/swift/blob/387580c995fc9844d4f268723bd55e22440b1a3d/stdlib/public/core/Sort.swift#L462)

A huge advantage is that it is easy to implement

It is more efficient than other quadratic running time sorting procedures such as bubble sort or selection sort

It is an adaptive algorithm – It speeds up when array is already substantially sorted

It is stable so preserves the order of the items with equal keys

Insertion sort is an in-place algorithm – does not need any additional memory

It is an online algorithm – It can sort an array as it receives the items for example downloading data from web

Hybrid algorithms uses insertion sort if the subarray is small enough: Insertion sort is faster for small subarrays than quicksort

Variant of insertion sort is shell sort

Sometimes selection sort is better: they are very similar algorithms

Insertion sort requires more writes because the inner loop can require shifting large sections of the sorted portion of the array

In general insertion sort will write to the array O(N^2) times while selection sort will write only O(N) times

For this reason selection sort may be preferable in cases where writing to memory is significantly more expensive than reading (such as with flash memory)

https://www.udemy.com/course/algorithms-and-data-structures-in-java-part-ii/learn/lecture/4901832#questions

screenshot 2024 05 15 at 6.27.12e280afpm
screenshot 2024 05 15 at 6.28.22e280afpm
screenshot 2024 05 15 at 6.28.35e280afpm
screenshot 2024 05 15 at 6.29.10e280afpm
screenshot 2024 05 15 at 6.29.36e280afpm
screenshot 2024 05 15 at 6.29.52e280afpm
func insertSort(_ input: inout [Int]) {
    var i = 1
    for i in 1..<input.count {
        var j = i
        while j-1 >= 0, input[j] < input[j-1] {
            input.swapAt(j, j-1)
            j-=1
        }
    }
}
var input = [-9, 4, -9, 5, 8, 12]
insertSort(&input)
print(input)


var input2 = [-9, 4, -9, 5, 8, 12, -249, 241, 4, 2, 12, 24140, 539, 3, 0, -2314]
insertSort(&input2)
print(input2)
screenshot 2024 05 15 at 9.20.55e280afpm

Merge Sort

Divide and Conquer

Break down into small subproblem, Use recursion.

It needs two functions

  • split – divide into two array
  • merge – merging two array into the sorted array
func mergeSort(input: inout [Int]) {
    //Base case
    if input.count == 1 {
        return
    }
    let midIndex = input.count / 2
    var left = Array(repeating: 0, count: midIndex)
    var right = Array(repeating: 0, count: input.count - midIndex)
    
    //Split into 2 sub array (create sub problems)
    split(input: &input, left: &left, right: &right)
    
    //Recursive - Divide and Conquer
    mergeSort(input: &left)
    mergeSort(input: &right)
    
    //Merge all together
    merge(input: &input, left: &left, right: &right)
}
func split(input: inout [Int], left: inout [Int], right: inout [Int]) {
    let leftCount = left.count
    for (index, item) in input.enumerated() {
        if index < leftCount {
            left[index] = item
        }
        else {
            right[index - leftCount] = item
        }
    }
}
func merge(input: inout [Int], left: inout [Int], right: inout [Int]) {
    var mergeIndex = 0
    var leftIndex = 0
    var rightIndex = 0
    
    while (leftIndex < left.count && rightIndex < right.count) {
        if left[leftIndex] < right[rightIndex] {
            input[mergeIndex] = left[leftIndex]
            leftIndex += 1
        }
        else if rightIndex < right.count {
            input[mergeIndex] = right[rightIndex]
            rightIndex += 1
        }
        mergeIndex += 1
    }
    if leftIndex < left.count {
        while mergeIndex < input.count {
            input[mergeIndex] = left[leftIndex]
            mergeIndex += 1
            leftIndex += 1
        }
    }
    if rightIndex < right.count {
        while mergeIndex < input.count {
            input[mergeIndex] = right[rightIndex]
            mergeIndex += 1
            rightIndex += 1
        }
    }
}
screenshot 2024 03 10 at 10.43.40e280afpm

Split Function

Calculate midIndex

Split them into two subarray

screenshot 2024 03 10 at 11.23.11e280afpm
screenshot 2024 03 11 at 10.38.33e280afpm
screenshot 2024 03 11 at 10.39.25e280afpm

Time complexity: O(nlogn)

Space complexity: O(n)

  • Because It needs a copy values when merging two array into the one sorted array

Quick Sort

func partition(_ input: inout [Int], low: Int, high: Int) -> Int {
    let pivot = input[low]
    var l = low
    var h = high
    while l < h {
        while (input[l] <= pivot && l < h) {
            l += 1
        }
        while (input[h] > pivot) {
            h -= 1
        }
        if l < h {
            input.swapAt(l, h)
        }
    }
    //Put pivot to the right position
    input.swapAt(low, h)
    return h
}

func quickSort(_ input: inout [Int], low: Int, high: Int) {
    //base
    if low >= high {
        return
    }
    //pivot
    let pivot = partition(&input, low: low, high: high)
    
    //left
    quickSort(&input, low: low, high: pivot - 1)

    //right
    quickSort(&input, low: pivot + 1, high: high)
}

var input = [2, 3, 1, 4, 9]
quickSort(&input, low: 0, high: input.count - 1)
print(input)
screenshot 2024 03 15 at 9.22.17e280afpm
screenshot 2024 03 15 at 9.44.45e280afpm

Quick Sort is also Divide and Conquer.

Time Complexity

  • Average Time Complexity is O(nlogn)

Space Complexity

  • O(logn) -> Extra space for the call stack in the recursive call
  • O(n) -> worst case

Binary Search

Search a sorted list!

//Binary Search
func findIndex(array: [Int], value: Int) -> Int {
    var min = 0
    var max = array.count - 1
    
    while min <= max {
        let mid = min + (max - min) / 2
        
        if value == array[mid] {
            return mid
        }
        //search right side
        if value > array[mid] {
            min = mid - 1
        }
        //search left side
        else {
            max = mid + 1
        }
    }
    return -1
}

let sortedArray = Array(0...100)
findIndex(array: sortedArray, value: 49)
findIndex(array: sortedArray, value: 78)
findIndex(array: sortedArray, value: 22)
findIndex(array: sortedArray, value: 89)
screenshot 2024 03 15 at 11.20.54e280afpm

Comments

One response to “Swift, Sorting and Binary Search Algorithm”

  1. […] Swift, Sorting and Binary Search Algorithm […]

Leave a Reply

Discover more from Shawn

Subscribe now to keep reading and get access to the full archive.

Continue reading