Unit 5
Introduction to Algorithms
The standard form of a quadratic equation is:
Ax2 + bx + c = 0, where
a, b and c are real numbers and
a != 0
The term b2-4ac is known as the discriminant of a quadratic equation. It tells the nature of the roots.
- If the discriminant is greater than 0, the roots are real and different.
- If the discriminant is equal to 0, the roots are real and equal.
- If the discriminant is less than 0, the roots are complex and different.
Program to Find Roots of a Quadratic Equation
#include <math.h>
#include <stdio.h>
Int main() {
Double a, b, c, discriminant, root1, root2, realPart, imagPart;
Printf("Enter coefficients a, b and c: ");
Scanf("%lf %lf %lf", &a, &b, &c);
Discriminant = b * b - 4 * a * c;
// condition for real and different roots
If (discriminant > 0) {
Root1 = (-b + sqrt(discriminant)) / (2 * a);
Root2 = (-b - sqrt(discriminant)) / (2 * a);
Printf("root1 = %.2lf and root2 = %.2lf", root1, root2);
}
// condition for real and equal roots
Else if (discriminant == 0) {
Root1 = root2 = -b / (2 * a);
Printf("root1 = root2 = %.2lf;", root1);
}
// if roots are not real
Else {
RealPart = -b / (2 * a);
ImagPart = sqrt(-discriminant) / (2 * a);
Printf("root1 = %.2lf+%.2lfi and root2 = %.2f-%.2fi", realPart, imagPart, realPart, imagPart);
}
Return 0;
}
Output
Enter coefficients a, b and c: 2.3
4
5.6
Root1 = -0.87+1.30i and root2 = -0.87-1.30i
In this program, the sqrt() library function is used to find the square root of a number.
First of all, how do we return multiple values from a C function? We can do it either using structures or pointers.
We have created a structure named pair (which contains min and max) to return multiple values.
Struct pair { Int min; Int max; }; |
And the function declaration becomes: struct pair getMinMax(int arr[], int n) where arr[] is the array of size n whose minimum and maximum are needed.
METHOD 1 (Simple Linear Search)
Initialize values of min and max as minimum and maximum of the first two elements respectively. Starting from 3rd, compare each element with max and min, and change max and min accordingly (i.e., if the element is smaller than min then change min, else if the element is greater than max then change max, else ignore the element)
/* structure is used to return two values from minMax() */ #include<stdio.h> Struct pair { Int min; Int max; };
Struct pair getMinMax(int arr[], int n) { Struct pair minmax; Int i;
/*If there is only one element then return it as min and max both*/ If (n == 1) { Minmax.max = arr[0]; Minmax.min = arr[0]; Return minmax; }
/* If there are more than one elements, then initialize min And max*/ If (arr[0] > arr[1]) { Minmax.max = arr[0]; Minmax.min = arr[1]; } Else { Minmax.max = arr[1]; Minmax.min = arr[0]; }
For (i = 2; i<n; i++) { If (arr[i] > minmax.max) Minmax.max = arr[i];
Else if (arr[i] < minmax.min) Minmax.min = arr[i]; }
Return minmax; }
/* Driver program to test above function */ Int main() { Int arr[] = {1000, 11, 445, 1, 330, 3000}; Int arr_size = 6; Struct pair minmax = getMinMax (arr, arr_size); Printf("nMinimum element is %d", minmax.min); Printf("nMaximum element is %d", minmax.max); Getchar(); } |
Output:
Minimum element is 1
Maximum element is 3000
Time Complexity: O(n)
In this method, total number of comparisons is 1 + 2(n-2) in worst case and 1 + n – 2 in best case.
In the above implementation, worst case occurs when elements are sorted in descending order and best case occurs when elements are sorted in ascending order.
METHOD 2 (Tournament Method)
Divide the array into two parts and compare the maximums and minimums of the two parts to get the maximum and the minimum of the whole array.
Pair MaxMin(array, array_size)
If array_size = 1
Return element as both max and min
Else if arry_size = 2
One comparison to determine max and min
Return that pair
Else /* array_size > 2 */
Recur for max and min of left half
Recur for max and min of right half
One comparison determines true max of the two candidates
One comparison determines true min of the two candidates
Return the pair of max and min
Implementation
/* structure is used to return two values from minMax() */ #include<stdio.h> Struct pair { Int min; Int max; };
Struct pair getMinMax(int arr[], int low, int high) { Struct pair minmax, mml, mmr; Int mid;
/* If there is only on element */ If (low == high) { Minmax.max = arr[low]; Minmax.min = arr[low]; Return minmax; }
/* If there are two elements */ If (high == low + 1) { If (arr[low] > arr[high]) { Minmax.max = arr[low]; Minmax.min = arr[high]; } Else { Minmax.max = arr[high]; Minmax.min = arr[low]; } Return minmax; }
/* If there are more than 2 elements */ Mid = (low + high)/2; Mml = getMinMax(arr, low, mid); Mmr = getMinMax(arr, mid+1, high);
/* compare minimums of two parts*/ If (mml.min < mmr.min) Minmax.min = mml.min; Else Minmax.min = mmr.min;
/* compare maximums of two parts*/ If (mml.max > mmr.max) Minmax.max = mml.max; Else Minmax.max = mmr.max;
Return minmax; }
/* Driver program to test above function */ Int main() { Int arr[] = {1000, 11, 445, 1, 330, 3000}; Int arr_size = 6; Struct pair minmax = getMinMax(arr, 0, arr_size-1); Printf("nMinimum element is %d", minmax.min); Printf("nMaximum element is %d", minmax.max); Getchar(); } |
Output:
Minimum element is 1
Maximum element is 3000
Time Complexity: O(n)
Total number of comparisons: let number of comparisons be T(n). T(n) can be written as follows:
Algorithmic Paradigm: Divide and Conquer
T(n) = T(floor(n/2)) + T(ceil(n/2)) + 2
T(2) = 1
T(1) = 0
If n is a power of 2, then we can write T(n) as:
T(n) = 2T(n/2) + 2
After solving above recursion, we get
T(n) = 3n/2 -2
Thus, the approach does 3n/2 -2 comparisons if n is a power of 2. And it does more than 3n/2 -2 comparisons if n is not a power of 2.
METHOD 3 (Compare in Pairs)
If n is odd then initialize min and max as first element.
If n is even then initialize min and max as minimum and maximum of the first two elements respectively.
For rest of the elements, pick them in pairs and compare their
maximum and minimum with max and min respectively.
#include<stdio.h>
/* structure is used to return two values from minMax() */ Struct pair { Int min; Int max; };
Struct pair getMinMax(int arr[], int n) { Struct pair minmax; Int i;
/* If array has even number of elements then Initialize the first two elements as minimum and Maximum */ If (n%2 == 0) { If (arr[0] > arr[1]) { Minmax.max = arr[0]; Minmax.min = arr[1]; } Else { Minmax.min = arr[0]; Minmax.max = arr[1]; } i = 2; /* set the startung index for loop */ }
/* If array has odd number of elements then Initialize the first element as minimum and Maximum */ Else { Minmax.min = arr[0]; Minmax.max = arr[0]; i = 1; /* set the startung index for loop */ }
/* In the while loop, pick elements in pair and Compare the pair with max and min so far */ While (i < n-1) { If (arr[i] > arr[i+1]) { If(arr[i] > minmax.max) Minmax.max = arr[i]; If(arr[i+1] < minmax.min) Minmax.min = arr[i+1]; } Else { If (arr[i+1] > minmax.max) Minmax.max = arr[i+1]; If (arr[i] < minmax.min) Minmax.min = arr[i]; } i += 2; /* Increment the index by 2 as two Elements are processed in loop */ }
Return minmax; }
/* Driver program to test above function */ Int main() { Int arr[] = {1000, 11, 445, 1, 330, 3000}; Int arr_size = 6; Struct pair minmax = getMinMax (arr, arr_size); Printf("nMinimum element is %d", minmax.min); Printf("nMaximum element is %d", minmax.max); Getchar(); } |
Output:
Minimum element is 1
Maximum element is 3000
Time Complexity: O(n)
Total number of comparisons: Different for even and odd n, see below:
If n is odd: 3*(n-1)/2
If n is even: 1 Initial comparison for initializing min and max,
And 3(n-2)/2 comparisons for rest of the elements
= 1 + 3*(n-2)/2 = 3n/2 -2
Second and third approaches make equal number of comparisons when n is a power of 2.
In general, method 3 seems to be the best.
A prime number is a whole number greater than 1, which is only divisible by 1 and itself. First few prime numbers are : 2 3 5 7 11 13 17 19 23 …..
Some interesting fact about Prime numbers
- Two is the only even Prime number.
- Every prime number can represented in form of 6n+1 or 6n-1 except 2 and 3, where n is natural number.
- Two and Three are only two consecutive natural numbers which are prime too.
- Goldbach Conjecture : Every even integer greater than 2 can be expressed as the sum of two primes.
- Wilson Theorem : Wilson’s theorem states that a natural number p > 1 is a prime number if and only if
- (p - 1) ! ≡ -1 mod p
OR (p - 1) ! ≡ (p-1) mod p
7. Fermat’s Little Theorem : If n is a prime number, then for every a, 1 <= a < n,
8. an-1 ≡ 1 (mod n)
9. OR
an-1 % n = 1
10. Prime Number Theorem : The probability that a given, randomly chosen number n is prime is inversely proportional to its number of digits, or to the logarithm of n.
11. Lemoine’s Conjecture : Any odd integer greater than 5 can be expressed as a sum of an odd prime (all primes other than 2 are odd) and an even semiprime. A semiprime number is a product of two prime numbers. This is called Lemoine’s conjecture.
How we check whether a number is Prime or not?
- Naive solution.
A naive solution is to iterate through all numbers from 2 to n-1 and for every number check if it divides n. If we find any number that divides, we return false.
// A school method based C++ program to // check if a number is prime #include <bits/stdc++.h> Using namespace std;
// function check whether a number // is prime or not Bool isPrime(int n) { // Corner case If (n <= 1) Return false;
// Check from 2 to n-1 For (int i = 2; i < n; i++) If (n % i == 0) Return false;
Return true; }
// Driver Program Int main() { IsPrime(11) ? cout << " true\n" : Cout << " false\n"; Return 0; } |
Output :
Time complexity :O(n)
True
Searching
Searching is the process of finding some particular element in the list. If the element is present in the list, then the process is called successful and the process returns the location of that element, otherwise the search is called unsuccessful.
There are two popular search methods that are widely used in order to search some item into the list. However, choice of the algorithm depends upon the arrangement of the list.
- Linear Search
- Binary Search
Linear Search
Linear search is the simplest search algorithm and often called sequential search. In this type of searching, we simply traverse the list completely and match each element of the list with the item whose location is to be found. If the match found then location of the item is returned otherwise the algorithm return NULL.
Linear search is mostly used to search an unordered list in which the items are not sorted. The algorithm of linear search is given as follows.
Algorithm
- LINEAR_SEARCH(A, N, VAL)
- Step 1: [INITIALIZE] SET POS = -1
- Step 2: [INITIALIZE] SET I = 1
- Step 3: Repeat Step 4 while I<=N
- Step 4: IF A[I] = VAL
SET POS = I
PRINT POS
Go to Step 6
[END OF IF]
SET I = I + 1
[END OF LOOP] - Step 5: IF POS = -1
PRINT " VALUE IS NOT PRESENTIN THE ARRAY "
[END OF IF] - Step 6: EXIT
Complexity of algorithm
Complexity | Best Case | Average Case | Worst Case |
Time | O(1) | O(n) | O(n) |
Space |
|
| O(1) |
C Program
- #include<stdio.h>
- Void main ()
- {
- Int a[10] = {10, 23, 40, 1, 2, 0, 14, 13, 50, 9};
- Int item, i,flag;
- Printf("\nEnter Item which is to be searched\n");
- Scanf("%d",&item);
- For (i = 0; i< 10; i++)
- {
- If(a[i] == item)
- {
- Flag = i+1;
- Break;
- }
- Else
- Flag = 0;
- }
- If(flag != 0)
- {
- Printf("\nItem found at location %d\n",flag);
- }
- Else
- {
- Printf("\nItem not found\n");
- }
- }
Output:
Enter Item which is to be searched
20
Item not found
Enter Item which is to be searched
23
Item found at location 2
Binary Search
Binary search is the search technique which works efficiently on the sorted lists. Hence, in order to search an element into some list by using binary search technique, we must ensure that the list is sorted.
Binary search follows divide and conquer approach in which, the list is divided into two halves and the item is compared with the middle element of the list. If the match is found then, the location of middle element is returned otherwise, we search into either of the halves depending upon the result produced through the match.
Binary search algorithm is given below.
BINARY_SEARCH(A, lower_bound, upper_bound, VAL)
- Step 1: [INITIALIZE] SET BEG = lower_bound
END = upper_bound, POS = - 1 - Step 2: Repeat Steps 3 and 4 while BEG <=END
- Step 3: SET MID = (BEG + END)/2
- Step 4: IF A[MID] = VAL
SET POS = MID
PRINT POS
Go to Step 6
ELSE IF A[MID] > VAL
SET END = MID - 1
ELSE
SET BEG = MID + 1
[END OF IF]
[END OF LOOP] - Step 5: IF POS = -1
PRINT "VALUE IS NOT PRESENT IN THE ARRAY"
[END OF IF] - Step 6: EXIT
Complexity
SN | Performance | Complexity |
1 | Worst case | O(log n) |
2 | Best case | O(1) |
3 | Average Case | O(log n) |
4 | Worst case space complexity | O(1) |
Example
Let us consider an array arr = {1, 5, 7, 8, 13, 19, 20, 23, 29}. Find the location of the item 23 in the array.
In 1st step :
- BEG = 0
- END = 8ron
- MID = 4
- a[mid] = a[4] = 13 < 23, therefore
In Second step:
- Beg = mid +1 = 5
- End = 8
- Mid = 13/2 = 6
- a[mid] = a[6] = 20 < 23, therefore;
In third step:
- Beg = mid + 1 = 7
- End = 8
- Mid = 15/2 = 7
- a[mid] = a[7]
- a[7] = 23 = item;
- Therefore, set location = mid;
- The location of the item will be 7.
Binary Search Program using Recursion
C program
- #include<stdio.h>
- Int binarySearch(int[], int, int, int);
- Void main ()
- {
- Int arr[10] = {16, 19, 20, 23, 45, 56, 78, 90, 96, 100};
- Int item, location=-1;
- Printf("Enter the item which you want to search ");
- Scanf("%d",&item);
- Location = binarySearch(arr, 0, 9, item);
- If(location != -1)
- {
- Printf("Item found at location %d",location);
- }
- Else
- {
- Printf("Item not found");
- }
- }
- Int binarySearch(int a[], int beg, int end, int item)
- {
- Int mid;
- If(end >= beg)
- {
- Mid = (beg + end)/2;
- If(a[mid] == item)
- {
- Return mid+1;
- }
- Else if(a[mid] < item)
- {
- Return binarySearch(a,mid+1,end,item);
- }
- Else
- {
- Return binarySearch(a,beg,mid-1,item);
- }
- }
- Return -1;
- }
Output:
Enter the item which you want to search
19
Item found at location 2
Bubble Sort
In Bubble sort, Each element of the array is compared with its adjacent element. The algorithm processes the list in passes. A list with n elements requires n-1 passes for sorting. Consider an array A of n elements whose elements are to be sorted by using Bubble sort. The algorithm processes like following.
- In Pass 1, A[0] is compared with A[1], A[1] is compared with A[2], A[2] is compared with A[3] and so on. At the end of pass 1, the largest element of the list is placed at the highest index of the list.
- In Pass 2, A[0] is compared with A[1], A[1] is compared with A[2] and so on. At the end of Pass 2 the second largest element of the list is placed at the second highest index of the list.
- In pass n-1, A[0] is compared with A[1], A[1] is compared with A[2] and so on. At the end of this pass. The smallest element of the list is placed at the first index of the list.
Algorithm :
- Step 1: Repeat Step 2 For i = 0 to N-1
- Step 2: Repeat For J = i + 1 to N - I
- Step 3: IF A[J] > A[i]
SWAP A[J] and A[i]
[END OF INNER LOOP]
[END OF OUTER LOOP - Step 4: EXIT
Complexity
Scenario | Complexity |
Space | O(1) |
Worst case running time | O(n2) |
Average case running time | O(n) |
Best case running time | O(n2) |
C Program
- #include<stdio.h>
- Void main ()
- {
- Int i, j,temp;
- Int a[10] = { 10, 9, 7, 101, 23, 44, 12, 78, 34, 23};
- For(i = 0; i<10; i++)
- {
- For(j = i+1; j<10; j++)
- {
- If(a[j] > a[i])
- {
- Temp = a[i];
- a[i] = a[j];
- a[j] = temp;
- }
- }
- }
- Printf("Printing Sorted Element List ...\n");
- For(i = 0; i<10; i++)
- {
- Printf("%d\n",a[i]);
- }
- }
Output:
Printing Sorted Element List . . .
7
9
10
12
23
34
34
44
78
101
C++ Program
- #include<iostream>
- Using namespace std;
- Int main ()
- {
- Int i, j,temp;
- Int a[10] = { 10, 9, 7, 101, 23, 44, 12, 78, 34, 23};
- For(i = 0; i<10; i++)
- {
- For(j = i+1; j<10; j++)
- {
- If(a[j] < a[i])
- {
- Temp = a[i];
- a[i] = a[j];
- a[j] = temp;
- }
- }
- }
- Cout <<"Printing Sorted Element List ...\n";
- For(i = 0; i<10; i++)
- {
- Cout <<a[i]<<"\n";
- }
- Return 0;
- }
Output:
Printing Sorted Element List ...
7
9
10
12
23
23
34
44
78
101
Selection Sort
Selection sort is a simple sorting algorithm. This sorting algorithm is an in-place comparison-based algorithm in which the list is divided into two parts, the sorted part at the left end and the unsorted part at the right end. Initially, the sorted part is empty and the unsorted part is the entire list.
The smallest element is selected from the unsorted array and swapped with the leftmost element, and that element becomes a part of the sorted array. This process continues moving unsorted array boundary by one element to the right.
This algorithm is not suitable for large data sets as its average and worst case complexities are of Ο(n2), where n is the number of items.
How Selection Sort Works?
Consider the following depicted array as an example.
For the first position in the sorted list, the whole list is scanned sequentially. The first position where 14 is stored presently, we search the whole list and find that 10 is the lowest value.
So we replace 14 with 10. After one iteration 10, which happens to be the minimum value in the list, appears in the first position of the sorted list.
For the second position, where 33 is residing, we start scanning the rest of the list in a linear manner.
We find that 14 is the second lowest value in the list and it should appear at the second place. We swap these values.
After two iterations, two least values are positioned at the beginning in a sorted manner.
The same process is applied to the rest of the items in the array.
Following is a pictorial depiction of the entire sorting process −
Now, let us learn some programming aspects of selection sort.
Algorithm
Step 1 − Set MIN to location 0
Step 2 − Search the minimum element in the list
Step 3 − Swap with value at location MIN
Step 4 − Increment MIN to point to next element
Step 5 − Repeat until list is sorted
Pseudocode
Procedure selection sort
List : array of items
n : size of list
For i = 1 to n - 1
/* set current element as minimum*/
Min = i
/* check the element to be minimum */
For j = i+1 to n
If list[j] < list[min] then
Min = j;
End if
End for
/* swap the minimum element with the current element*/
If indexMin != i then
Swap list[min] and list[i]
End if
End for
End procedure
Analysis of efficiency of an algorithm can be performed at two different stages, before implementation and after implementation, as
A priori analysis − This is defined as theoretical analysis of an algorithm. Efficiency of algorithm is measured by assuming that all other factors e.g. Speed of processor, are constant and have no effect on implementation.
A posterior analysis − This is defined as empirical analysis of an algorithm. The chosen algorithm is implemented using programming language. Next the chosen algorithm is executed on target computer machine. In this analysis, actual statistics like running time and space needed are collected.
Algorithm analysis is dealt with the execution or running time of various operations involved. Running time of an operation can be defined as number of computer instructions executed per operation.
Algorithm Complexity
Suppose X is treated as an algorithm and N is treated as the size of input data, the time and space implemented by the Algorithm X are the two main factors which determine the efficiency of X.
Time Factor − The time is calculated or measured by counting the number of key operations such as comparisons in sorting algorithm.
Space Factor − The space is calculated or measured by counting the maximum memory space required by the algorithm.
The complexity of an algorithm f(N) provides the running time and / or storage space needed by the algorithm with respect of N as the size of input data.
Space Complexity
Space complexity of an algorithm represents the amount of memory space needed the algorithm in its life cycle.
Space needed by an algorithm is equal to the sum of the following two components
A fixed part that is a space required to store certain data and variables (i.e. simple variables and constants, program size etc.), that are not dependent of the size of the problem.
A variable part is a space required by variables, whose size is totally dependent on the size of the problem. For example, recursion stack space, dynamic memory allocation etc.
Space complexity S(p) of any algorithm p is S(p) = A + Sp(I) Where A is treated as the fixed part and S(I) is treated as the variable part of the algorithm which depends on instance characteristic I. Following is a simple example that tries to explain the concept
Algorithm
SUM(P, Q)
Step 1 - START
Step 2 - R ← P + Q + 10
Step 3 - Stop
Here we have three variables P, Q and R and one constant. Hence S(p) = 1+3. Now space is dependent on data types of given constant types and variables and it will be multiplied accordingly.
Time Complexity
Time Complexity of an algorithm is the representation of the amount of time required by the algorithm to execute to completion. Time requirements can be denoted or defined as a numerical function t(N), where t(N) can be measured as the number of steps, provided each step takes constant time.
For example, in case of addition of two n-bit integers, N steps are taken. Consequently, the total computational time is t(N) = c*n, where c is the time consumed for addition of two bits. Here, we observe that t(N) grows linearly as input size increases.