0% found this document useful (0 votes)
19 views

Assignment 4

Heap sort uses a binary heap data structure to sort elements in O(n log n) time. Binary heaps support operations like insert, delete, extract max/min, and decrease key in O(log n) time, making them useful for implementing priority queues. Priority queues have many applications in graph algorithms. Counting sort can only sort integers within a fixed range, so it cannot directly sort strings alphabetically. Radix sort can sort integers between 0 to 9999 by sorting based on individual digits using counting sort. Separate chaining handles hash table collisions by storing colliding elements in a linked list at the same hash table index, while linear probing resolves collisions by searching for the next empty index in the hash table.

Uploaded by

Sheetal Anand
Copyright
© © All Rights Reserved
Available Formats
Download as DOCX, PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
19 views

Assignment 4

Heap sort uses a binary heap data structure to sort elements in O(n log n) time. Binary heaps support operations like insert, delete, extract max/min, and decrease key in O(log n) time, making them useful for implementing priority queues. Priority queues have many applications in graph algorithms. Counting sort can only sort integers within a fixed range, so it cannot directly sort strings alphabetically. Radix sort can sort integers between 0 to 9999 by sorting based on individual digits using counting sort. Separate chaining handles hash table collisions by storing colliding elements in a linked list at the same hash table index, while linear probing resolves collisions by searching for the next empty index in the hash table.

Uploaded by

Sheetal Anand
Copyright
© © All Rights Reserved
Available Formats
Download as DOCX, PDF, TXT or read online on Scribd
You are on page 1/ 21

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)

You might also like