Sorting, Searching, and The Complexity
Sorting, Searching, and The Complexity
the complexities
Pengurutan, pencarian, dan kompleksitas
Just like the movement of air bubbles in the water that rise up to the surface,
each element of the array move to the end in each iteration. Therefore, it is called
a bubble sort.
bubbleSort(array)
for i <- 1 to indexOfLastUnsortedElement-1
if leftElement > rightElement
swap leftElement and rightElement
end bubbleSort
Code
void bubbleSort(int array[], int size) {
// loop to access each array element
for (int step = 0; step < size; ++step) {
// loop to compare array elements
for (int i = 0; i < size - step; ++i) {
// compare two adjacent elements
if (array[i] > array[i + 1]) {
// swapping elements if elements are not in the intended order
int temp = array[i];
array[i] = array[i + 1];
array[i + 1] = temp;
}
}
}
}
Selection Sort
Selection sort is a sorting algorithm that selects the smallest element from an
unsorted list in each iteration and places that element at the beginning of the
unsorted list.
selectionSort(array, size)
repeat (size - 1) times
set the first unsorted element as the minimum
for each of the unsorted elements
if element < currentMinimum
set element as new minimum
swap minimum with first unsorted position
end selectionSort
Code
insertionSort(array)
mark first element as sorted
for each unsorted element X
'extract' the element X
for j <- lastSortedIndex down to 0
if current element j > X
move sorted element to the right by 1
break loop and insert X here
end insertionSort
Code
After that, the merge function comes into play and combines the sorted arrays into larger
arrays until the whole array is merged.
To sort an entire array, we need to call MergeSort(A, 0, length(A)-1).
//The algorithm maintains three pointers, one for each of the two arrays
//and one for maintaining the current index of the final sorted array.
// Divide the array into two subarrays, sort them and merge them
void mergeSort(int arr[], int l, int r) {
if (l < r) {
// m is the point where the array is divided into two subarrays
int m = l + (r - l) / 2;
mergeSort(arr, l, m);
mergeSort(arr, m + 1, r);
// Merge the sorted subarrays
merge(arr, l, m, r);
}
}
// Merge two subarrays L and M into arr
void merge(int arr[], int p, int q, int r) {
// Create L ← A[p..q] and M ← A[q+1..r]
int n1 = q - p + 1;
int n2 = r - q;
int L[n1], M[n2];
for (int i = 0; i < n1; i++)
L[i] = arr[p + i];
for (int j = 0; j < n2; j++)
M[j] = arr[q + 1 + j];
// Maintain current index of sub-arrays and main array
int i, j, k;
i = 0; j = 0; k = p;
// Until we reach either end of either L or M, pick larger among
// elements L and M and place them in the correct position at A[p..r]
while (i < n1 && j < n2) {
if (L[i] <= M[j]) {
arr[k] = L[i];
i++;
} else {
arr[k] = M[j];
j++;
}
k++;
}
// When we run out of elements in either L or M,
// pick up the remaining elements and put in A[p..r]
while (i < n1) {
arr[k] = L[i];
i++;
k++;
}
while (j < n2) {
arr[k] = M[j];
j++;
k++;
}
}
Quick Sort
Quicksort is a sorting algorithm based on the divide and conquer approach
where:
• An array is divided into subarrays by selecting a pivot element (element
selected from the array).
• While dividing the array, the pivot element should be positioned in such a way
that elements less than pivot are kept on the left side and elements greater
than pivot are on the right side of the pivot.
• The left and right subarrays are also divided using the same approach. This
process continues until each subarray contains a single element.
• At this point, elements are already sorted. Finally, elements are combined to
form a sorted array.
Working of Quicksort Algorithm
1. Select the Pivot Element
There are different variations of quicksort where the pivot element is selected from different positions. Here,
we will be selecting the rightmost element of the array as the pivot element.
3. Divide Subarrays
• Pivot elements are again chosen for the left and the right sub-parts separately. And, step 2 is repeated.
When to use?
• For searching operations in smaller arrays (<100 items).
Code
Binary Search Algorithm can be implemented in two ways which are discussed below.
• Iterative Method
• Recursive Method
The recursive method follows the divide and conquer approach.
• In libraries of Java, .Net, C++ STL
• While debugging, the binary search is used to pinpoint the place where the error
happens.
Algorithms
1. Time Complexity: Time complexity refers to the time taken by an algorithm to complete its execution
with respect to the size of the input. It can be represented in different forms:
• Big-O notation (O)
• Omega notation (Ω)
• Theta notation (Θ)
2. Space Complexity: Space complexity refers to the total amount of memory used by the algorithm
for a complete execution. It includes both the auxiliary memory and the input.
The auxiliary memory is the additional space occupied by the algorithm apart from the input data.
Usually, auxiliary memory is considered for calculating the space complexity of an algorithm.
Asymptotic Analysis
The efficiency of an algorithm depends on the amount of time, storage and other
resources required to execute the algorithm. The efficiency is measured with the
help of asymptotic notations.
An algorithm may not have the same performance for different types of inputs.
With the increase in the input size, the performance will change.
The study of change in performance of the algorithm with the change in the
order of the input size is defined as asymptotic analysis.
Asymptotic Notations
Asymptotic notations are the mathematical notations used to describe the running time of
an algorithm when the input tends towards a particular value or a limiting value.
For example: In bubble sort, when the input array is already sorted, the time taken by the
algorithm is linear i.e. the best case.
But, when the input array is in reverse condition, the algorithm takes the maximum time
(quadratic) to sort the elements i.e. the worst case.
When the input array is neither sorted nor in reverse order, then it takes average time.
These durations are denoted using asymptotic notations.
Bubble Sort n n2 n2 1
Selection Sort n2 n2 n2 1
Insertion Sort n n2 n2 1
Selection Sort No
Quicksort No
Heap Sort No
Shell Sort No
Big-O Notation (O-notation)
Big-O notation represents the upper bound of the running time of an algorithm. Thus, it gives the
worst-case complexity of an algorithm.
The above expression can be described as a function f(n) belongs to the set O(g(n)) if there exists a
positive constant c such that it lies between 0 and cg(n), for sufficiently large n.
For any value of n, the running time of an algorithm does not cross the time provided by O(g(n)).
Since it gives the worst-case running time of an algorithm, it is widely used to analyze an algorithm
as we are always interested in the worst-case scenario.
Big-O Notation (O-notation)
Big-O gives the upper bound of a function
Omega Notation (Ω-notation)
Omega notation represents the lower bound of the running time of an algorithm. Thus, it
provides the best case complexity of an algorithm.
Ω(g(n)) = { f(n): there exist positive constants c and n0 such that 0 ≤ cg(n) ≤ f(n) for all n ≥
n0 }
The above expression can be described as a function f(n) belongs to the set Ω(g(n)) if
there exists a positive constant c such that it lies above cg(n), for sufficiently large n.
For any value of n, the minimum time required by the algorithm is given by
Omega Ω(g(n)).
Omega Notation (Ω-notation)
Omega gives the lower bound of a function
Theta Notation (Θ-notation)
Theta notation encloses the function from above and below. Since it represents the upper
and the lower bound of the running time of an algorithm, it is used for analyzing the
average-case complexity of an algorithm.
For a function g(n), Θ(g(n)) is given by the relation:
Θ(g(n)) = { f(n): there exist positive constants c1, c2 and n0 such that 0 ≤ c1g(n) ≤ f(n) ≤
c2g(n) for all n ≥ n0 }
The above expression can be described as a function f(n) belongs to the set Θ(g(n)) if
there exist positive constants c1 and c2 such that it can be sandwiched
between c1g(n) and c2g(n), for sufficiently large n.
If a function f(n) lies anywhere in between c1g(n) and c2g(n) for all n ≥ n0, then f(n) is said
to be asymptotically tight bound.
References
Programmiz.com
w3schools.com