ASSIGNMENT 4:
1)Sort English words in alphabetical order using heap sort (C++ language):
#include <iostream>
#include <string>
using namespace std;
int x = -1;
string heap[1000];
void heapForm(string k)
x++;
heap[x] = k;
int child = x;
string tmp;
int index = x / 2;
while (index >= 0) {
if (heap[index] > heap[child]) {
tmp = heap[index];
heap[index] = heap[child];
heap[child] = tmp;
child = index;
index = index / 2;
else {
break;
void heapSort()
int left1, right1;
while (x >= 0) {
string k;
k = heap[0];
cout << k << " ";
heap[0] = heap[x];
x = x - 1;
string tmp;
int index = 0;
int length = x;
left1 = 1;
right1 = left1 + 1;
while (left1 <= length) {
if (heap[index] <= heap[left1]
&& heap[index] <= heap[right1]) {
break;
else {
if (heap[left1] < heap[right1]) {
tmp = heap[index];
heap[index] = heap[left1];
heap[left1] = tmp;
index = left1;
else {
tmp = heap[index];
heap[index] = heap[right1];
heap[right1] = tmp;
index = right1;
left1 = 2 * left1;
right1 = left1 + 1;
}
void sort(string k[], int n)
for (int i = 0; i < n; i++) {
heapForm(k[i]);
heapSort();
int main()
string arr[] = { "banana", "orange", "apple",
"pineapple", "mango", "litchi" };
int n = sizeof(arr) / sizeof(arr[0]);
sort(arr, n);
2) What are the uses of binary heaps?
I) Heap Sort uses Binary Heap to sort an array in O(nLogn) time.
II) Priority queues can be efficiently implemented using Binary Heap because it supports insert(),
delete() and extractmax(), decreaseKey() operations in O(logn) time. Binomial Heap and Fibonacci
Heap are variations of Binary Heap. These variations perform union also efficiently.
III) The priority queues are especially used in Graph Algorithm.
IV) Many problems can be efficiently solved using Heaps
// A C++ program to demonstrate common Binary Heap Operations
#include<iostream>
#include<climits>
using namespace std;
// Prototype of a utility function to swap two integers
void swap(int *x, int *y);
// A class for Min Heap
class MinHeap
int *harr; // pointer to array of elements in heap
int capacity; // maximum possible size of min heap
int heap_size; // Current number of elements in min heap
public:
// Constructor
MinHeap(int capacity);
// to heapify a subtree with the root at given index
void MinHeapify(int );
int parent(int i) { return (i-1)/2; }
// to get index of left child of node at index i
int left(int i) { return (2*i + 1); }
// to get index of right child of node at index i
int right(int i) { return (2*i + 2); }
// to extract the root which is the minimum element
int extractMin();
// Decreases key value of key at index i to new_val
void decreaseKey(int i, int new_val);
// Returns the minimum key (key at root) from min heap
int getMin() { return harr[0]; }
// Deletes a key stored at index i
void deleteKey(int i);
// Inserts a new key 'k'
void insertKey(int k);
};
// Constructor: Builds a heap from a given array a[] of given size
MinHeap::MinHeap(int cap)
heap_size = 0;
capacity = cap;
harr = new int[cap];
// Inserts a new key 'k'
void MinHeap::insertKey(int k)
if (heap_size == capacity)
cout << "\nOverflow: Could not insertKey\n";
return;
// First insert the new key at the end
heap_size++;
int i = heap_size - 1;
harr[i] = k;
// Fix the min heap property if it is violated
while (i != 0 && harr[parent(i)] > harr[i])
swap(&harr[i], &harr[parent(i)]);
i = parent(i);
}
// Decreases value of key at index 'i' to new_val. It is assumed that
// new_val is smaller than harr[i].
void MinHeap::decreaseKey(int i, int new_val)
harr[i] = new_val;
while (i != 0 && harr[parent(i)] > harr[i])
swap(&harr[i], &harr[parent(i)]);
i = parent(i);
// Method to remove minimum element (or root) from min heap
int MinHeap::extractMin()
if (heap_size <= 0)
return INT_MAX;
if (heap_size == 1)
heap_size--;
return harr[0];
// Store the minimum value, and remove it from heap
int root = harr[0];
harr[0] = harr[heap_size-1];
heap_size--;
MinHeapify(0);
return root;
// This function deletes key at index i. It first reduced value to minus
// infinite, then calls extractMin()
void MinHeap::deleteKey(int i)
decreaseKey(i, INT_MIN);
extractMin();
// A recursive method to heapify a subtree with the root at given index
// This method assumes that the subtrees are already heapified
void MinHeap::MinHeapify(int i)
int l = left(i);
int r = right(i);
int smallest = i;
if (l < heap_size && harr[l] < harr[i])
smallest = l;
if (r < heap_size && harr[r] < harr[smallest])
smallest = r;
if (smallest != i)
swap(&harr[i], &harr[smallest]);
MinHeapify(smallest);
// A utility function to swap two elements
void swap(int *x, int *y)
int temp = *x;
*x = *y;
*y = temp;
// Driver program to test above functions
int main()
MinHeap h(11);
h.insertKey(3);
h.insertKey(2);
h.deleteKey(1);
h.insertKey(15);
h.insertKey(5);
h.insertKey(4);
h.insertKey(45);
cout << h.extractMin() << " ";
cout << h.getMin() << " ";
h.decreaseKey(2, 1);
cout << h.getMin();
return 0;
}
3) Is it possible to sort English words in alphabetical order using counting sort?
The counting sort algorithm is designed to sort integer values that are in a fixed range, so it cant
be applied to sort strings. For a counting sort you’ve got to reduce the item being sorted to a single
number. Depending upon the size of number you choose, you can encode the first 2–8 characters
into a number that would compare favourably with the items adjacent to it in the sorted list, but if
those characters are identical, you are going to need a larger number to resolve their true sort order.
Eventually you have to compare every char in the string which is very time consuming. In such cases,
radix sort can be used.
4) Write counting sort to sort upper case English characters (C++ language):
#include <bits/stdc++.h>
using namespace std;
#define MAX 26
void alternateSort(string& s)
int n = s.length();
int lCount[MAX] = { 0 }, uCount[MAX] = { 0 };
for (int i = 0; i < n; i++) {
if (isupper(s[i]))
uCount[s[i] - 'A']++;
else
lCount[s[i] - 'a']++;
int i = 0, j = 0, k = 0;
while (k < n) {
while (i < MAX && uCount[i] == 0)
i++;
if (i < MAX) {
s[k++] = 'A' + i;
uCount[i]--;
while (j < MAX && lCount[j] == 0)
j++;
if (j < MAX) {
s[k++] = 'a' + j;
lCount[j]--;
}
}
int main()
string str = "bAwutndekWEdkd";
alternateSort(str);
cout << str << "\n";
}
5) Write radix sort to sort positive integers between 0 to 9999 ( C language):
#include<stdio.h>
int get_max (int a[], int n){
int max = a[0];
for (int i = 1; i < n; i++)
if (a[i] > max)
max = a[i];
return max;
void radix_sort (int a[], int n){
int bucket[10][10], bucket_cnt[10];
int i, j, k, r, NOP = 0, divisor = 1, lar, pass;
lar = get_max (a, n);
while (lar > 0){
NOP++;
lar /= 10;
for (pass = 0; pass < NOP; pass++){
for (i = 0; i < 10; i++){
bucket_cnt[i] = 0;
for (i = 0; i < n; i++){
r = (a[i] / divisor) % 10;
bucket[r][bucket_cnt[r]] = a[i];
bucket_cnt[r] += 1;
i = 0;
for (k = 0; k < 10; k++){
for (j = 0; j < bucket_cnt[k]; j++){
a[i] = bucket[k][j];
i++;
divisor *= 10;
printf ("After pass %d : ", pass + 1);
for (i = 0; i < n; i++)
printf ("%d ", a[i]);
}
}
int main (){
int i, n, a[10000];
for (i = 0; i < 10000; i++)
radix_sort (a, n);
printf ("Sorted items : ");
for (i = 0; i < 10000; i++)
printf ("%d ", a[i]);
return 0;
}
6) Separate chaining:
The idea behind separate chaining is to implement the array as a linked list called a chain.
Separate chaining is one of the most popular and commonly used techniques in order to handle
collisions.
Here, all those elements that hash into the same slot index are inserted into a linked list. Now, we
can use a key K to search in the linked list by just linearly traversing. If the intrinsic key for any
entry is equal to K then it means that we have found our entry. If we have reached the end of the
linked list and yet we haven’t found our entry then it means that the entry does not exist. Hence,
the conclusion is that in separate chaining, if two different elements have the same hash value
then we store both the elements in the same linked list one after the other.
Linear probing:
The simplest approach to resolve a collision is linear probing. In this technique, if a value is already
stored at a location generated by h(k), it means collision occurred then we do a sequential search to
find the empty location.
Here the idea is to place a value in the next available position. Because in this approach searches are
performed sequentially so it’s known as linear probing.
Here array or hash table is considered circular because when the last slot reached an empty location
not found then the search proceeds to the first location of the array. Clustering is a major drawback
of linear probing.
Quadratic probing:
Quadratic probing is an open addressing scheme in computer programming for resolving the hash
collisions in hash tables. Quadratic probing operates by taking the original hash index and adding
successive values of an arbitrary quadratic polynomial until an open slot is found. An example
sequence using quadratic probing is:
H + 12, H + 22, H + 32, H + 42, H + 52 .... H + k2
Quadratic probing can be a more efficient and optimal algorithm in an open addressing table, since it
avoids the clustering problem that can occur with linear probing, although it is not immune. It also
provides good memory caching because it preserves some locality of reference; however, linear
probing has greater locality and, thus, better cache performance.
Load factor:
For the first step, the time taken depends on the K and the hash function.
For example, if the key is a string “abcd”, then it’s hash function may depend on the length of the
string. But for very large values of n, the number of entries into the map, and length of the keys is
almost negligible in comparison to n so hash computation can be considered to take place in
constant time, i.e, O(1).
For the second step, traversal of the list of K-V pairs present at that index needs to be done. For
this, the worst case may be that all the n entries are at the same index. So, time complexity would
be O(n). But, enough research has been done to make hash functions uniformly distribute the keys
in the array so this almost never happens.
So, on an average, if there are n entries and b is the size of the array there would be n/b entries
on each index. This value n/b is called the load factor that represents the load that is there on our
map.
This Load Factor needs to be kept low, so that number of entries at one index is less and so is the
complexity almost constant, i.e., O(1).
Rehashing:
As the name suggests, rehashing means hashing again. Basically, when the load factor increases
to more than its pre-defined value (default value of load factor is 0.75), the complexity increases.
So to overcome this, the size of the array is increased (doubled) and all the values are hashed
again and stored in the new double-sized array to maintain a low load factor and low complexity.
Rehashing is done because whenever key value pairs are inserted into the map, the load factor
increases, which implies that the time complexity also increases as explained above. This might
not give the required time complexity of O(1).
Hence, rehash must be done, increasing the size of the bucketArray so as to reduce the load factor
and the time complexity.
7) UNION AND FIND algorithm with an example (C++ language):
Example:
Initially array size and Arr look like:
Arr[9] = {0, 1, 2, 3, 4, 5, 6, 7, 8}
size[9] = {1, 1, 1, 1, 1, 1, 1, 1, 1}
Consider the edges in the graph, and add them one by one to the disjoint-union set as follows:
Edge 1: 0-1
find(0)=>0, find(1)=>1, both have different root parent
Put these in single connected component as currently they doesn't belong to different connected
components.
Arr[1]=0, size[0]=2;
Edge 2: 0-2
find(0)=>0, find(2)=>2, both have different root parent
Arr[2]=0, size[0]=3;
Edge 3: 1-3
find(1)=>0, find(3)=>3, both have different root parent
Arr[3]=0, size[0]=3;
Edge 4: 3-4
find(3)=>1, find(4)=>4, both have different root parent
Arr[4]=0, size[0]=4;
Edge 5: 2-4
find(2)=>0, find(4)=>0, both have same root parent
Hence, There is a cycle in graph.
We stop further checking for cycle in graph.
Program:
#include <bits/stdc++.h>
using namespace std;
const int MAX_VERTEX = 101;
int Arr[MAX_VERTEX];
int size[MAX_VERTEX];
void initialize(int n)
for (int i = 0; i <= n; i++) {
Arr[i] = i;
size[i] = 1;
}
int find(int i)
while (Arr[i] != i)
Arr[i] = Arr[Arr[i]];
i = Arr[i];
return i;
void _union(int xr, int yr)
if (size[xr] < size[yr])
Arr[xr] = Arr[yr];
size[yr] += size[xr];
else
Arr[yr] = Arr[xr];
size[xr] += size[yr];
}
int isCycle(vector<int> adj[], int V)
for (int i = 0; i < V; i++) {
for (int j = 0; j < adj[i].size(); j++) {
int x = find(i);
int y = find(adj[i][j]);
if (x == y)
return 1;
_union(x, y);
return 0;
int main()
int V = 3;
initialize(V);
vector<int> adj[V];
adj[0].push_back(1);
adj[0].push_back(2);
adj[1].push_back(2);
if (isCycle(adj, V))
cout << "Graph contains Cycle.\n";
else
cout << "Graph does not contain Cycle.\n";
return 0;
}
Time Complexity(Find) : O(log*(n))
Time Complexity(union) : O(1)
Auxiliary Space: O(Max_Vertex)
9)