---
# System prepended metadata

title: 03 Sorting Algorithms
tags: [Algorithm]

---

03 Sorting Algorithms
===
###### tags: `Algorithm`
這篇筆記是我參考【演算法圖鑑】和網路資源所製作。如果內容有誤，還請不吝賜教!
## Table of Content
[ToC]
## **Selection Sort - 選擇排序**
### *Operation*
1. Find the minimum value in the sequence.
2. Switch it with the leftmost item.
3. Repeating 1 - 2 until the sequence is sorted.
### *Time Complexity*
#### $O(n^2)$
### *Implementation*
```python=
import random as rd
from math import inf
from time import time as t


def slsort(n: list, reverse=False):
    sort = n
    for i in range(len(n) - 1):
        mi = [0, inf]
        for j in range(i, len(n)):
            if sort[j] < mi[1]:
                mi = [j, sort[j]]
        sort[i], sort[mi[0]] = sort[mi[0]], sort[i]
    if reverse:
        return sort[::-1]
    else:
        return sort


print(slsort(list(rd.randint(1, 50) for i in range(10))))

# execution time
n = [i for i in range(1000, 0, -1)]
start = t()

for cases in range(50):
    slsort(n)

print((t() - start) / 50)
# time : 0.0551131010055542 second
```
## **Insertion Sort - 插入排序**
### *Operations*
1. Start from the left item, and keep moving onto the right term. Let's say the index for this step is $i$.
2. Insert $S_i$ ($S$ is the sequence) in the subsequence $S' = \{S_0, ..., S_{i-1}\}$ so that $S'$ is still in ascending/decreasing order.
3. Repeat 1 - 2 until the sequence is sorted.
### *Time Complexity*
#### $O(n^2)$
### *Implementation*
```python=
import random as rd
from time import time as t


def insort(n: list, reverse=False):
    sort = n
    for i in range(len(sort)):
        for j in range(i):
            if sort[j] > sort[i]:
                sort.insert(j, sort.pop(i))
    if reverse:
        return sort[::-1]
    else:
        return sort


print(insort(list(rd.randint(1, 50) for i in range(10))))

# execution time
n = [i for i in range(1000, 0, -1)]
start = t()

for cases in range(50):
    insort(n)

print((t() - start) / 50)
# time : 0.05066179275512695 second
```
## **Bubble Sort - 氣泡排序**
### *Operations*
(P.S. following shows how to sort the sequence in ascending order)
1. Start from the left item, and keep moving onto the right term. Let's say the index for this step is $i$.
2. Compare $S_i$ and $S_{i+1}$. If $S_i > S_{i+1}$, then swap these two items. Otherwise do nothing.
3. Repeat 1 - 2 until $S_i$ is the last item. Then $i$ = 0 again.
4. Repeat 1 - 3 until $S$ is sorted.
### *Time Complexity*
#### $O(n^2)$
### *Implementation*
```python=
import random as rd
from time import time as t


def bbsort(n: list, reverse=False):
    sort = n
    for i in range(len(n) - 1, 0, -1):
        for j in range(i):
            if sort[j] > sort[j + 1]:
                sort[j], sort[j + 1] = sort[j + 1], sort[j]
    if reverse:
        return sort[::-1]
    else:
        return sort


print(bbsort(list(rd.randint(1, 10) for i in range(10))))

# execution time
n = [i for i in range(1000, 0, -1)]
start = t()

for cases in range(50):
    bbsort(n)

print((t() - start) / 50)
# time : 0.061395759582519534 second
```
## **Quick Sort - 快速排序**
### *Operations*
:::info
Quick sort requires a very important algorithm - D&C (**Divide and Conquer**). D&C means for questions that are divisible, we can divide them into plenty of subproblems, and **solutions for subproblems can help us infer, and resolve the original problem**.
:::
1. Randomly choose an item $N$ in the sequence $S$.
2. Set up 2 empty lists $L$ and $R$.
3. Let $i$ be the iterator, traverse throught the whole $S$, For every $i$ smaller than $N$, push it into $L$. Otherwise push it into $R$.
4. Recursion : call this function again for $L$ and $R$ so that : 
> sort($S$) = sort($L$) + [$N$] + sort($R$)
**P.S.** Boundary conditions of sort() : 
> define sort($S$):
> if length of $S \leq 1$:
>     return $S$
### *Time Complexity*
#### $O(n\log n)$ ~ $O(n^2)$
Average complexity of quick sort is $O(n\log n)$.
However, worst case $O(n^2)$ happens if we keep selevting the smallest or the biggest item as $N$.
### *Implementation*
```python=
import sys
import random as rd
from time import time as t

sys.setrecursionlimit(10000)

def qksort(n: list, reverse=False):
    sort = n

    if len(sort) <= 1:
        return sort

    left, right = [], []
    for i in sort[1:]:
        if i < sort[0]:
            left.append(i)
        else:
            right.append(i)

    sort = qksort(left) + [sort[0]] + qksort(right)
    if reverse:
        return sort[::-1]
    else:
        return sort


print(qksort(list(rd.randint(1, 50) for i in range(10))))

# execution time
# 1. average case
n = list(rd.randint(1, 10000) for i in range(1000))
start = t()

for cases in range(50):
    qksort(n)

print((t() - start) / 50)
# time : 0.0017751836776733398 second
# Way more faster than bubble sort!!!

# 2. worst case
n = [i for i in range(1000, 0, -1)]
start = t()

for cases in range(50):
    qksort(n)

print((t() - start) / 50)
# time : 0.06175748348236084 second
# As slow as selection sort :(
```
## **Merge Sort - 合併排序**
### *Operations*
:::info
Similar to quick sort, merge-sort uses **D&C** and **recursion** likewisely.
:::
1. Divide sequence $S$ into two same-length subsequence $L$ and $R$.
2. Recursion : call sort() again to sort the two parts.
3. Combine $L$ and $R$ together. -> Finish!
> sort($S$) = merge(sort($L$) + sort($R$))
**P.S.** Definition of sort() : 
> 
> define sort($S$):
> &emsp; if length of $S \leq 1$:
> &emsp; &emsp; return $S$
> &emsp; Divide $S$ into $L$ and $R$
> &emsp; Sort($L$) and Sort($R$)
> &emsp; sorted_$S$ = Merge($L$, $R$)
> &emsp; return sorted_$S$
### *Time Complexity*
#### $O(n\ log\ n)$
### *Implementation*
```python=
import sys
import random as rd
from time import time as t

sys.setrecursionlimit(10000)


def mgsort(n: list, reverse=False) -> list:
    sort = n

    if len(sort) <= 1:
        return sort

    mid = int(len(sort) / 2 + 0.5)
    l, r = mgsort(sort[:mid]), mgsort(sort[mid:])
    sort = []

    while l and r:
        if l[0] < r[0]:
            sort.append(l.pop(0))
        else:
            sort.append(r.pop(0))

    sort.extend(l + r)
    if reverse:
        return sort[::-1]
    else:
        return sort


print(mgsort(list(rd.randint(1, 50) for i in range(10))))

# execution time
n = [i for i in range(1000, 0, -1)]
start = t()

for cases in range(50):
    mgsort(n)

print((t() - start) / 50)
# time : 0.0025319910049438478 second
# Way more faster than bubble sort!!!
```
## **Heap Sort - 堆積排序**
### *Features of heap*
1. Heap is a binary tree.
2. $\forall$ parent node $N_p$ , it is always smaller than every node on its child tree.
3. From 2, it's very clear that the root node is the smallest node in a heap.
### *Operations*
1. Create an empty list $S$.
2. Use given data to make a heap.
3. Take out root node, push it into $S$
4. Refresh the whole heap.
5. Repeat 3 - 4 until all nodes are popped out i.e. the list $S$ forms a sorted sequence.
### *Time Complexity*
#### $O(n\ log\ n)$