Basics to Algorithms

Subjects: Algorithms and Data Structures
Links: Asymptotic notation

Insertion Sort

Our first algorithm, insertion sort, solves the sorting problem:

Input: A sequence of n numbers a1,a2,,an
Output: A permutation a1,a2,,an of the input of sequence such that a1a2an

The numbers we wish to sort are also known as the keys.

Insertion sort is an efficient algorithm for sorting a small number of elements, or mostly sorted lists. The way insertion sort works is by expanding an ordered sub-list by finding where on the ordered sub-list the next elements place, and slowly expanding it.

The algorithms sorts the input numbers in in place: it rearranges the numbers within the array A, with at most a constant number of them stored outside the array at any time.

#include <iostream>
#include <vector>

void insertion_sortvector<int>& arr{
	int n = arr.size();
	for (int i = 1; i < n; ++i)
	{
		int key = arr[i];
		int j = i -1;

		while (j >= 0 && arr[j] > key) {
			arr[j+1] = arr[j];
			--j;
		}
		arr[j+1] = key;
	}
}

int main(){
	std::vector<int> arr = {12, 11, 13, 5 ,6};
	insertion_sort(arr);

	std::cout << "sorted array: ";
	for (size_t i = 0; i < arr.size(); i++)
		std::cout << arr[i] << ' ';
	std::cout << std::endl;
	// output on console: sorted array: 5 6 11 12 13 
	return 0;
}

Loop invariants and the correctness of insertion sort

We use loop invariants to help us understand why an algorithm is correct. We must show three things about a loop invariant:

This is similar to finite mathematical induction.

Divide-and-conquer approach

Many useful algorithms are recursive in structure: to solve a given problem they call themselves recursively one or more times to deal with closely related sub-problems.

These algorithms typically follow a divide-and-conquer approach: they break the problem into several sub-problems that are similar to the original problem but smaller in size, solve the sub-problems recursively, and then combine these solutions to create a solution to the original problem

The divide-and-conquer are involves three steps at each level of recursion:

These are the basic guidelines for many important algorithms in CS

Merge Sort

The merge sort algorithm closely follows the divide-and-conquer paradigm:

The key to Merge sort, is the merging of two sorted sequences to get another sorted sequence. We can call an auxiliary procedure Merge(A,p,q,r) where A is an array, and p,q,r are indices into the array such that pq<r. The procedure assumes that the subarrays A[p,,q] and A[q+1,,r] are sorted in ordered. It merges them to form a single sorted subarray that replaces the current A[p,,r].

This Merge procedure takes time Θ(n), where n=rp+1 is the total of elements being merged.

#include <iostream>
#include <vector>

void Mergevector<int>& A, int p, int q, int r
{
	int n1 = q-p+1;
	int n2 = r-q;
	
	std::vector<int> L(n1);
	std::vector<int> R(n2);
	
	for (int i = 0; i < n1; ++i)
		L[i] = A[p+i];
	for (int i = 0; i <  n2; ++i)
		R[i] = A[q + i+1];
	int i = 0;
	int j = 0;
	int k = p;

	// We use a while loop to check we are in the arrays at the same time
	while (i < n1 && j < n2)
	{
		if (L[i] <= R[j])
		{
			A[k] = L[i];
			++i;
		}
		else
		{
			A[k] = R[j];
			++j;
		}
		++k;
	}
	// This is used when we still have items left on L
	while (i < n1)
	{
		A[k] = L[i];
		++i;
		++k;
	}
	// This is used when we still have items left on R
	while (j < n2)
	{
		A[k] = R[j];
		++j;
		++k;
	}
}

void Merge_sortvector<int>& A, int p, int r
{
	if (p < r)
	{
		int q = (p+r)/2;
		Merge_sort(A, p, q); 
		Merge_sort(A, q+1, r);
		Merge(A, p, q, r);
	}
}

int main(){
	std::vector<int> arr = {12, 11, 13, 5 ,6};
	Merge_sort(arr, 0, arr.size()-1);

	std::cout << "sorted array: ";
	for (size_t i = 0; i < arr.size(); i++)
		std::cout << arr[i] << ' ';
	std::cout << std::endl;
	// output on console: sorted array: 5 6 11 12 13 
	return 0;
}

The procedure Merge as a subroutine in the merge sort algorithm. The Merge_sort(A,p,r) sorts the elements of the subarray A[p,,r] into two sub arrays n/2 elements, and A[q+1,,r] containing n/2 elements

We will later prove that the time for merge sort in asymptotic notation is Θ(nlogn).