CS221L - Data Structures and Algorithms Lab Manual
CS221L - Data Structures and Algorithms Lab Manual
I
Week Contents/Topics
Week 2 Structures
Week 4 Stacks
Week 5 Queues
Week 6 Sorting I
Week 7 Sorting II
Week 10 Graphs
Week 11 Hashing
II
HARDWARE REQUIREMENT
Core i3 or Above
2 GB RAM
20 GB HDD
SOFTWARE REQUIREMENT
Windows 8x or above
Dev C++ IDE / Visual Studio
III
Overall Grading Policy
Assessment Items Percentage
Lab Evaluation 30%
Project 10%
Midterm Exam 20%
Final Exam 40%
Rubric
Every lab task shall contain one or more tasks and every question shall be evaluated on the
following criteria:
Running: 2
Runs without exception: 1
Interactive menu: 0.5
Displays proper messages/operations clearly: 0.5
Completeness: 3
Sub task 1: 1
Sub task 2: 1
Sub task 3: 1
Accuracy: 4
Appropriate data structure: 1
Efficient (Computational Complexity) algorithms: 1
Passes all test cases: 1
Viva: 1
Clarity 1 (No credit, if not relevant implementation):
Indentation: 0.5
Meaningful/relevant variable/function names: 0.5
*the grading policy is tentative and could be changed depending on the instructor suit.
IV
Students are expected to read respective lab contents before attending it
Since the contents of a lab are covered in class before practicing in the lab hence,
students are expected to read, understand and run all examples and sample problems
before attending the lab
Reach the lab on time. Late comers shall not be allowed to attend the lab
Students need to maintain 100% attendance in lab if not a strict action will be taken.
Wear your student card in the lab
Mobile phones, USBs, and other electronic devices are strictly prohibited in the lab
In case of hardware/power issues, immediately inform lab attendant and do not attempt
to fix it by yourself
In case of queries during lab task implementation, raise your hand, inform your
instructor and wait for your turn
V
1.1. Introduction
In previous labs we have learned and practiced C++ variables and arrays. We know that every
variable has an associated data type, name, address and value. For example
int x = 10;
Here, x is the name of a variable. The data type of x is integer and its value is 10. So, what is
the address of variable x? Every variable is stored in memory at a specific location in memory.
The starting address of the memory location where a variable is stored, is called the address of
that variable. In C++ the address of a variable can be accessed using & operator. For example:
int x = 10;
cout<<&x;
A pointer is a variable that can store memory address of another variable. We call them pointers
because they “point” to another variable. Like any other variable, pointer variable has a data
type, name, address and value. Unlike other variables the value of a pointer variable is address
of another variable. Another important difference of pointer from normal variable is the size.
The size of a variable depends upon the data type of that variable but every pointer variable
has same size.
DataType * Pointer;
DataType can be an int, float, char, or anyother user defined data type
* pronounced as “star” is an operator that tells the compiler that the variable declared
is of type pointer
Pointer is the name of pointer variable it can be any valid variable name in C++
In Example 1-1, ptrX is a pointer variable of type int. So, it can store the address of an integer
type variable. We stored address of variable x in pointer variable ptrX. So, the output of &x
and ptrX is same 0x24fe3c (i.e. the address of variable x).
1
#include<iostream>
using namespace std;
int main()
{
int x = 10;
int *ptrX;
ptrX = &x;
cout<<"x = "<<x<<endl;
cout<<"&x = "<<&x<<endl;
cout<<"ptrX = "<<ptrX<<endl;
cout<<"&ptrX = "<<&ptrX<<endl;
return 0;
}
Output
x = 10
&x = 0x24fe3c
ptrX = 0x24fe3c
&ptrX = 0x24fe30
There are two roles of asterisk operator in pointers: declaration and de-referencing.
The asterisk operator is used for declaration of a pointer that differentiates it from other
variables as explained in previous section. Please note that if you use * anywhere else in your
code (after pointer declaration) before the pointer name then it is evaluated as de-referencing
operator by the compiler.
The asterisk operator is used as a de-reference operator which allows the compiler to jump to
the memory location specified by the value of a pointer (which is the address of another
variable) and get the value. In Example 1-2, x is a variable and ptrX is a pointer. The address
of variable x is stored in ptrX. *ptrX gives us the value of variable x.
2
using namespace std;
int main()
{
int x = 10;
int *ptrX;
ptrX = &x;
cout<<"x = "<<x<<endl;
cout<<"*ptrX = "<<*ptrX<<endl<<endl;
cout<<"&x = "<<&x<<endl;
cout<<"ptrX = "<<ptrX<<endl;
return 0;
}
Output
x = 10
*ptrX = 10
&x = 0x24fe34
ptrX = 0x24fe34
As we know that ptrX points towards variable x. If we update the value of x then *ptrX shows
us the updated value and vice versa Example 1-3.
int main()
{
int x = 10;
int *ptrX = &x;
cout<<"x = "<<x<<endl;
cout<<"*ptrX = "<<*ptrX<<endl<<endl;
x = 20;
cout<<"After updating the value of x"<<endl;
cout<<"x = "<<x<<endl;
cout<<"*ptrX = "<<*ptrX<<endl<<endl;
3
cout<<"After updating the value of *ptrX"<<endl;
*ptrX = 30;
cout<<"x = "<<x<<endl;
cout<<"*ptrX = "<<*ptrX<<endl<<endl;
return 0;
}
Output
x = 10
*ptrX = 10
Pointer arithmetic is different from regular variable (int, float etc.) arithmetic. When we
increment a pointer by 1, it increments by the size of its data type. Moreover, only addition and
subtraction are allowed in pointers. Let us examine pointer arithmetic by an Example 1-4.
Pointer ptrX is pointing towards an int type variable x and pointer ptrY is pointing towards a
double type variable y. The address of x and value of ptrX in this example is 0x24fe30. The
output of ptrX+1 is 0x24fe34, not 0x24fe31, why? Because, when we increment a pointer
variable by 1, it increments by the size of its data type. In this case data type of ptrX is int and
size of int is 4 bytes. So, ptrX+1 gives the output 0x24fe34. We can see that initial value of
ptrY is 0x24fe20 and ptrY+1 gives 0x24fe28 output because of its data type double that has
size 8 bytes, in this case. Addition, subtraction and decrement operations also work the same
way for pointers. Students should try it.
int main()
{
int x = 10;
double y = 20;
4
int *ptrX = &x;
double *ptrY = &y;
cout<<"ptrX = "<<ptrX<<endl;
cout<<"ptrX+1 = "<<ptrX+1<<endl;
cout<<"ptrX+2 = "<<ptrX+2<<endl;
cout<<endl;
cout<<"ptrY = "<<ptrY<<endl;
cout<<"ptrY+1 = "<<ptrY+1<<endl;
cout<<"ptrY+2 = "<<ptrY+2<<endl;
cout<<endl;
ptrY = 0x24fe20
ptrY+1 = 0x24fe28
ptrY+2 = 0x24fe30
Size of int = 4
Size of double = 8
Pointers in C/C++ can point to a variable of any data type as well as other pointers too. We can
store the address of a pointer in another pointer. This is called pointer to pointer access method.
Consider the following graphical explanation.
5
Variable a is an integer variable which is stored at memory location 100 and its value is 10. ptr
is an integer type pointer which is stored at 200 and it points to memory location 100. Pointer
variable ptp is stored at 300 and points to a memory location 200 i.e. points to another pointer
ptr. Please note that *ptr means the value of variable a (10), *ptp means value of ptr, which is
the address of variable a (100). Few important points to note here are:
cout<<ptp;
Shows the address 200 (value of double pointer ptp)
cout<<*ptp;
results in 100 (value stored at address 200 - dereferencing)
cout<<**ptp;
results in 10 (value stored at address 100 – two level dereferencing)
Students are suggested to experiment pointers and double pointers by displaying addresses and
values using cout statements as shown in Example 1-5.
int main()
{
int a = 10;
int *ptr = &a;
int **ptp = &ptr;
cout<<"a = "<<a<<endl;
cout<<"&a = "<<&a<<endl;
cout<<"ptr = "<<ptr<<endl;
cout<<"&ptr = "<<&ptr<<endl;
cout<<"ptp = "<<ptp<<endl;
cout<<"&ptp = "<<&ptp<<endl;
cout<<endl;
You can access both addresses and values of all relevant variables using
ptp
Address of ptp using &ptp = 0x24fe28
Value of ptp (address of ptr) using ptp = 0x24fe30
Value of ptr (address of a) using *ptp = 0x24fe3c
Value of a using **ptp = 10
We have covered functions in our previous labs. We know that there are two ways to pass
arguments to a function: call by value and call by reference. Call by value copies the value of
an argument into the formal parameter of the subroutine. Any changes made to the parameter
have no effect on the original argument passed to the function. In call by reference, the address
of an argument is copied into the parameter. The address is used to access the actual argument
used in the call. This means that changes made to the parameter affect the original variable
passed as argument. There is a third way, call by pointer. Like call by reference, original
variable passed to function gets updated. The syntax of all three function calls is explained in
Example 1-6.
7
Example 1-6 Call value, call by reference and call by pointer
Code
#include<iostream>
using namespace std;
int main()
{
int x = 5;
cout<<" Initially"<<endl;
cout<<"x = "<<x<<endl;
cout<<endl;
callByValue(x);
cout<<"After call by value"<<endl;
cout<<"x = "<<x<<endl;
cout<<endl;
callByReference(x);
cout<<"After call by reference"<<endl;
cout<<"x = "<<x<<endl;
cout<<endl;
callByPointer(&x);
cout<<"After call by pointer"<<endl;
cout<<"x = "<<x<<endl;
return 0;
}
Output
Initially
x = 5
8
1.3. Pointers and Arrays
There are two important concepts to remember while studying the relationship between
pointers and arrays:
Array elements are always stored in contiguous memory locations.
Array name is a pointer to the first element in the array but mind it; array name is a
constant pointer on which normal rules of pointers are not applied.
return 0;
}
Output
Enter 5 numbers: 1
5
2
4
3
Sum = 15
int main () {
// an array with 5 elements.
double balance[5] = {1000.0, 2.0, 3.4, 17.0, 50.0};
9
double *p;
p = balance;
return 0;
}
Output
Array values using pointer
*(p + 0) : 1000
*(p + 1) : 2
*(p + 2) : 3.4
*(p + 3) : 17
*(p + 4) : 50
Array values using balance as address
*(balance + 0) : 1000
*(balance + 1) : 2
*(balance + 2) : 3.4
*(balance + 3) : 17
*(balance + 4) : 50
Obviously, a question arises as to which of the above two methods should be used when?
Accessing array elements by pointers is always faster than accessing them subscripts. However,
from the point of view of convenience in programming subscript method is used.
We already know that on mentioning the name of the array we get its base address. Thus by
saying *num we would be able to refer to the zeroth element of the array, that is 1. One can
easily see that *num and *(num+0) both refer to 1. Similarly, by saying *(num+1) we can refer
to the first element that is 2. What the C++ compiler does internally is that when we say num[i]
it converts the expression to *(num+i).
10
i[num] (This notation may appear strange but amazingly it gives the
sameresult. Don’t understand it just as a concept, try it out in your
code)
So far, we have studied static arrays. The size of static arrays cannot be changed on run time.
Therefore, dynamic array is used. The static declaration of an array requires the size of the
array to be known in advance. What if the actual size of the list exceeds its expected size?
Solution?
1.4.1. Declaration
int * foo;
foo = new int [5];
Problem Statement
Write a C++ program that manages a list of values using the concept of dynamic memory
allocation. Your program must have following operations (implemented using a separate
function):
1. Insert an element
a. Beginning
b. End
c. Given index (optional/not mandatory)
2. Find an element
3. Show all elements
a. From first to last
b. From last to first (optional/not mandatory)
4. Delete an Element
a. You will prompt user to enter the number to be deleted
b. Use search method to find the index
c. Perform delete operation
Solution
#include<iostream>
using namespace std;
class List{
11
private:
int *students;
int used; // stores used size of array
int maxSize;
public:
List()
{
cout<<"Enter size of the list:";
cin>>this->maxSize;
students = new int[this->maxSize];
this->used = 0;
}
/*
* Find an element
* return index if element found otherwise return -1
*/
int find(int student)
{
int N = this->used;
12
int index = -1;
/*
* Display all elements start to end
*/
void showAll(){
if(this->used == 0)
{
cout<<"List is empty"<<endl;
return;
}
int N = this->used;
for(int i=0; i < N; i++)
{
cout<<"["<<i<<"] => "<<this->students[i]<<endl;
}
} //showAll ends
/*
* Display all elements end to start
*/
void showAllReverse(){
if(this->used == 0)
{
cout<<"List is empty"<<endl;
return;
}
int N = this->used;
for(int i=N-1; i >= 0; i--)
{
cout<<"["<<i<<"] => "<<this->students[i]<<endl;
}
} //showAll ends
/*
* display
* index
*/
int display(int index)
{
if(index > this->used)
{
cout<<"Invalid index"<<endl;
}else{
cout<<"["<<index<<"] => "<<this-
>students[index]<<endl;
}
}
/*
13
* remove a student
*
*/
void remove(int student)
{
int index = this->find(student);
if(index >= 0){
this->display(index);
int confirm = 0;
cout<<"Do you really want to delete it?"<<endl;
cout<<"1. Yes"<<endl;
cout<<"Anyother number. No"<<endl;
cout<<"Enter your choice: ";
cin>>confirm;
if(confirm == 1)
{
for(int i=index; i < this->used-1; i++)
{
this->students[i] = this-
>students[i+1];
}
this->used--;
cout<<"student delete successfuly"<<endl;
}
}else{
cout<<"student not found"<<endl;
}
}
if(index < 0)
{
cout<<"Index cannot be negative"<<endl;
return false;
}
return true;
}
int showMenu();
void printShashkaLine(char, int);
int main()
{
List students;
int choice;
bool again = true;
int student;
int index;
14
while(again){
choice = showMenu();
switch(choice)
{
case 0: //exit program
again = false;
break;
if(!students.validIndex(index))
{
break;
}
cout<<"Enter student: ";
cin>>student;
students.insert(student, index);
break;
default:
15
cerr<<"Invalid choice"<<endl;
cout<<"Kindly select a valid number from
menu"<<endl;
break;
}
system("pause");
system("cls");
}
cout<<"Thank you for using our program"<<endl;
}
int showMenu()
{
int choice;
int numbers = 0;
printShashkaLine('=', 50);
cout<<"\t\t My Program Menu"<<endl;
printShashkaLine('=', 50);
printShashkaLine('=', 50);
cout<<"Enter your choice: ";
cin>>choice;
return choice;
}
16
2.1. Introduction
There are many instances in programming where we need more than one variable in order to
represent something. For example, to represent yourself, you might want to store your name,
your birthday, your height, your weight, or any other number of characteristics about yourself.
Structures are a way of storing many different values in variables of potentially different types
under the same name. This makes it a more modular program, which is easier to modify because
its design makes things more compact. Structs are generally useful whenever a lot of data needs
to be grouped together--for instance, they can be used to hold records from a database or to
store information about contacts in an address book. In the contacts example, a struct could be
used that would hold all the information about a single contact--name, address, phone number,
and so forth.
As structure is a group of data elements grouped together under one name. These data elements,
known as members, can have different types and different lengths. Data structures are declared
in C++ using the following syntax:
struct structure_name {
member_type1 member_name1;
member_type2 member_name2;
member_type3 member_name3;
.
.
} object_names;
where structure_name is a name for the structure type, object_name can be a set of valid
identifiers for objects that have the type of this structure. Within braces { } there is a list with
the data members, each one is specified with a type and a valid identifier as its name.
The first thing we must know is that a data structure creates a new type: Once a data structure
is declared, a new type with the identifier specified as structure_name is created and can be
used in the rest of the program as if it was any other type. For example:
struct product{
int weight;
float price;
};
product apple;
product banana, melon;
It is important to clearly differentiate between what is the structure type name, and what is an
object (variable) that has this structure type. We can instantiate many objects (i.e. variables,
like apple, banana and melon) from a single structure type (product).
17
Once we have declared our three objects of a determined structure type
(apple, banana and melon) we can operate directly with their members. To do that we use a
dot (.) inserted between the object name and the member name. For example, we could
operate with any of these elements as if they were standard variables of their respective
types:
apple.weight
apple.price
banana.weight
banana.price
melon.weight
melon.price
Each one of these has the data type corresponding to the member they refer to: apple.weight,
banana.weight and melon.weight are of type int, while apple.price, banana.price and
melon.price are of type float.
Here is an example program:
struct Books
{
char title[100];
char author[50];
char subject[100];
int book_id;
};
int main()
{
//2 possible ways to declare a structure variable
struct Books book1;
Books book2;
18
cout<<endl;
cout<<"Book2 title : " << book2.title<<endl;
cout<<"Book2 author : " << book2.author<<endl;
cout<<"Book2 subject : " << book2.subject<<endl;
cout<<"Book2 id: " << book2.book_id<<endl;
}
Output
Book1 title : Data Structures using C++
Book1 author : DS Malik
Book1 subject : Programming
Book1 id: 1234
You can pass a structure as a function argument in very similar way as you pass any other
variable or pointer. You would access structure variables in the similar way as you have
accessed in the above example:
#include<iostream>
#include<cstring>
using namespace std;
struct Books
{
char title[100];
char author[50];
char subject[100];
int book_id;
};
int main()
{
//2 possible ways to declare a structure variable
struct Books book1;
Books book2;
19
book1.book_id = 1234;
cout<<"Book1: "<<endl;
printBook(book1);
cout<<endl<<"Book2: "<<endl;
printBook(book2);
When a structure contains another structure, it is called nested structure. For example, we have
two structures named Address and Employee. To make Address nested to Employee, we have
to define Address structure before and outside Employee structure and create an object of
Address structure inside Employee structure.
Syntax for structure within structure or nested structure
struct structure1
{
- - - - - - - - - -
- - - - - - - - - -
};
struct structure2
{
- - - - - - - - - -
- - - - - - - - - -
20
struct structure1 obj;
};
struct movies{
string title;
int year;
};
struct friends{
string name;
string email;
movies fav_mov;
}frnd1, frnd2;
int main()
{
frnd1.name = "Charlie";
frnd1.fav_mov.title = "Harry Potter";
frnd1.fav_mov.year = 2001;
cout<<"Name: "<<frnd1.name;
cout<<endl<<"Fav mov: "<<frnd1.fav_mov.title<<endl;
cout<<"Movie year: "<<frnd1.fav_mov.year<<endl<<endl;
pfriends->name = "Maria";
pfriends->fav_mov.title = "Tangled";
pfriends->fav_mov.year = 2010;
pfriends->email = "[email protected]";
cout<<"Name: "<<pfriends->name;
cout<<endl<<"Fav mov: "<< pfriends->fav_mov.title <<endl;
cout<<"Movie Year: "<<pfriends->fav_mov.year <<endl;
cout<<"Email: "<<pfriends->email<<endl;
}
Output
Name: Charlie
Fav mov: Harry Potter
Movie year: 2001
Name: Maria
Fav mov: Tangled
Movie Year: 2010
Email: [email protected]
We have a structure:
struct employee{
char name[30];
int age;
float salary;
};
A pointer to a structure must be initialized before it can be used anywhere in program. The
address of a structure variable can be obtained by applying the address operator & to the
variable. For example, the statement
sptr1 = &emp1;
The members of a structure can be accessed using an arrow operator. The arrow operator ->
(consisting of minus sign (-) followed by the greater than (>) symbol). Using the arrow
operator, a structure member can be accessed by the expression
sptr1->member-name
Where sptr holds the address of a structure variable, and member-name is the name of a
member belonging to the structure. For example, the values held by the members
belonging to the structure emp1 are given by the expression:
22
sptr1->name
sptr1->age
sptr1->salary
struct movies_t {
string title;
int year;
};
int main ()
{
string mystr;
movies_t amovie;
movies_t * pmovie;
pmovie = &amovie;
return 0;
}
Output
Enter title: Invasion of the body snatchers
Enter year: 1978
23
2.5. Example Problems
Solution
#include<iostream>
#include<cstdlib>
using namespace std;
struct Student{
string name;
int regNo;
};
int main()
{
const int SIZE = 3;
Student students[SIZE];
initialize(students, SIZE);
display(students, SIZE);
return 0;
}
24
cout<<"------------------------------"<<endl;
cout<<"\tList of students "<<endl;
cout<<"------------------------------"<<endl;
cout<<"Reg. No\t\tName"<<endl;
cout<<"------------------------------"<<endl;
for(int i=0; i < SIZE; i++)
{
cout<<students[i].regNo<<"\t\t"<<students[i].name<<endl;
}
}
Output
Read Data
------------------------------
Student 1
------------------------------
------------------------------
Student 2
------------------------------
------------------------------
Student 3
------------------------------
------------------------------
List of students
------------------------------
Reg. No Name
------------------------------
1 Ali
2 Ahmad
3 Talha
--------------------------------
25
3.1. Introduction
Like arrays, Linked List is a linear data structure. Unlike arrays, linked list elements are not
stored at a contiguous location; the elements are linked using pointers.
Arrays can be used to store linear data of similar types, but arrays have the following
limitations.
1) The size of the arrays is fixed: So we must know the upper limit on the number of elements
in advance. Also, generally, the allocated memory is equal to the upper limit irrespective of the
usage.
2) Inserting a new element in an array of elements is expensive because the room has to be
created for the new elements and to create room existing elements have to be shifted.
And if we want to insert a new ID 1005, then to maintain the sorted order, we have to move all
the elements after 1000 (excluding 1000).
Deletion is also expensive with arrays until unless some special techniques are used. For
example, to delete 1010 in id[], everything after 1010 has to be moved.
1) Dynamic size
2) Ease of insertion/deletion
3.1.3. Drawbacks
1) Random access is not allowed. We have to access elements sequentially starting from the
first node. So we cannot do binary search with linked lists efficiently with its default
implementation. Read about it here.
2) Extra memory space for a pointer is required with each element of the list.
3) Not cache friendly. Since array elements are contiguous locations, there is locality of
reference which is not there in case of linked lists.
26
3.2. Types of Linked List
It is the most common. Each node has data and a pointer to the next node.
We add a pointer to the previous node in a doubly linked list. Thus, we can go in either
direction: forward or backward.
A circular linked list is a variation of linked list in which the last element is linked to the first
element. This forms a circular loop.
for singly linked list, next pointer of last item points to the first item
In doubly linked list, prev pointer of first item points to last item as well.
27
3.3. Single Linked List Operations
Now that you have got an understanding of the basic concepts behind linked list and their types,
it’s time to dive into the common operations that can be performed.
In all of the examples, we will assume that the linked list has three nodes 1 --->2 --->3 with
node structure as below:
struct node
{
int data;
struct node *next;
};
Displaying the contents of a linked list is very simple. We keep moving the temp node to the
next one and display its contents.
When temp is NULL, we know that we have reached the end of linked list so we get out of the
while loop.
You can add elements to either beginning, middle or end of linked list.
Add to beginning
28
Change next of new node to point to head
Change head to point to recently created node
29
Delete from beginning
head = head->next;
Delete from end
Here is the complete program for all the linked list operations we learnt till now. Lots of edge
cases have been left out to make the program short.
struct Node{
int value;
Node *next;
Node(){
this->next = NULL;
}
};
class SingleLinkedList{
private:
Node *head;
public:
30
SingleLinkedList(){ //initially set the empty list
parameters
head = new Node;
}
void printList(){
cout<<endl<<"Linked List"<<endl<<endl;
cout<<"[head] -> ";
for(Node *tmp=head->next; tmp != NULL; tmp = tmp->next)
{
cout<<"["<<tmp->value<<"]"<<" -> ";
}
cout<<"null"<<endl<<endl;
}
tmp->next=prevNode->next;
prevNode->next = tmp;
31
//if value is found
if(prevNode->next->value == v){
found = true;
break;
}
if(found){
if(findPrev)
return prevNode;
else
return prevNode->next;
}
else
return NULL;
Node *prevN;
Node *tmp;
tmp = prevN->next;
prevN->next = prevN->next->next;
delete tmp;
return true;
}
else
return false;
}
};
int main(){
SingleLinkedList list;
int choice;
int value;
Node* node;
bool again = true;
while(again)
{
cout<<"========== Menu =========="<<endl;
cout<<"0. Exit"<<endl;
cout<<"1. Display"<<endl;
cout<<"2. addStart"<<endl;
cout<<"3. addEnd"<<endl;
cout<<"4. addAfter"<<endl;
cout<<"5. Find Value"<<endl;
cout<<"6. Remove Value"<<endl;
cout<<"Enter choice: ";
32
cin>>choice;
switch(choice)
{
case 0: //exit loop
again = false;
break;
case 1: // display list
list.printList();
break;
if(node == NULL)
{
cout<<"Value "<<value<<" not found!"<<endl;
}else{
cout<<"Enter the value that you want to
enter after "<<value<<": ";
cin>>value;
list.addAfter(node, value);
}
break;
if(list.find(value) != NULL)
{
cout<<"Value exists in the list"<<endl;
}else{
cout<<"Value does not exist in the
list"<<endl;
}
break;
case 6: // remove
cout<<"Enter the value that you want to remove:
";
cin>>value;
if(list.remove(value))
33
{
cout<<value<<" removed successfully"<<endl;
}else{
cout<<value<<" does not exist in the
list"<<endl;
}
break;
}
system("pause");
system("CLS");
}
}
Output
========== Menu ==========
0. Exit
1. Display
2. addStart
3. addEnd
4. addAfter
5. Find Value
6. Remove Value
Enter choice: 1
Linked List
struct Node
{
int value;
Node *next, *previous; //now we have 2 pointers
Node()
{
this->next = NULL;
this->previous = NULL;
}
};
class doublyLinkedList
{
34
private:
Node *head;
Node *tail;
public:
//initially set the empty list parameters
doublyLinkedList()
{
head = new Node;
tail = NULL;
}
void printList()
{
cout<<endl<<"Linked List"<<endl<<endl;
cout<<"[head] <-> ";
for(Node *tmp=head->next; tmp != NULL; tmp = tmp->next)
{
cout<<"["<<tmp->value<<"]"<<" <-> ";
}
cout<<"null"<<endl<<endl;
}
void addStart(int v)
{
Node *tmp = new Node; //create new node to be added
tmp->value = v; //assign a value
tmp->previous = head; // pointing to head at
previously
if(tail == NULL)
{
tail = tmp;
}
tmp->next = head->next; //new node next pointer (tmp)
is now pointing to the same as head
head->next = tmp; //head next pointer is updated
to a new node
void addEnd(int v)
{
Node *tmp = new Node; //create new node
tmp->value = v; //set the value
tmp->previous = tail; //tail is now the 2nd last node
so previously pointing to tail
else
{
35
tail->next = tmp; //tail next pointer is updated
now which was null
tail = tail->next; //set the new tail to tmp
i.e. new node
}
}
void addAfter(Node *prevNode, int val)
{
Node *tmp = new Node;
tmp->value = val;
else
{
prevNode->next->previous = tmp;
tmp->next=prevNode->next;
prevNode->next = tmp;
}
tmp->previous = prevNode;
}
if(found)
return tmp;
else
return NULL;
36
//check for value existance
if( delNode != NULL){
else {
tmp->next = delNode->next;
delNode->next->previous = tmp;
delete delNode;
break;
return true;
}
}
}
else
return false;
}
void reversePrint(){
37
cout<<"5. Find Value"<<endl;
cout<<"6. Remove Value"<<endl;
cout<<"Enter choice: ";
cin>>choice;
switch(choice)
{
case 0: //exit loop
again = false;
break;
case 1: // display list
list.printList();
break;
if(node == NULL)
{
cout<<"Value "<<value<<" not found!"<<endl;
}else{
cout<<"Enter the value that you want to
enter after "<<value<<": ";
cin>>value;
list.addAfter(node, value);
}
break;
if(list.find(value) != NULL)
{
cout<<"Value exists in the list"<<endl;
}else{
cout<<"Value does not exist in the
list"<<endl;
}
break;
case 6: // remove
cout<<"Enter the value that you want to remove:
";
38
cin>>value;
if(list.remove(value))
{
cout<<value<<" removed successfully"<<endl;
}else{
cout<<value<<" does not exist in the
list"<<endl;
}
break;
}
system("pause");
system("CLS");
}
}
Output
========== Menu ==========
0. Exit
1. Display
2. addStart
3. addEnd
4. addAfter
5. Find Value
6. Remove Value
Enter choice: 1
Linked List
39
4.1. What is Stack Data Structure?
Stack is an abstract data type with a bounded(predefined) capacity. It is a simple data structure
that allows adding and removing elements in a particular order. Every time an element is added,
it goes on the top of the stack and the only element that can be removed is the element that is
at the top of the stack, just like a pile of objects.
The simplest application of a stack is to reverse a word. You push a given word to stack - letter
by letter - and then pop letters from the stack.
1. Parsing
2. Expression Conversion(Infix to Postfix, Postfix to Prefix etc)
Below we have a simple C++ program implementing stack data structure while following the
object-oriented programming concepts.
# include<iostream>
class Stack
{
int top;
public:
int a[10]; //Maximum size of Stack
Stack()
{
top = -1;
}
41
cout << "Element Inserted \n";
}
}
// main function
int main() {
Stack s1;
s1.push(10);
s1.push(100);
/*
preform whatever operation you want on the stack
*/
}
Output
Element Inserted
Element Inserted
Below mentioned are the time complexities for various operations that can be performed on
the Stack data structure.
Push Operation: O(1)
Pop Operation: O(1)
Top Operation: O(1)
Search Operation: O(n)
42
The time complexities for push() and pop() functions are O(1) because we always have to
insert or remove the data from the top of the stack, which is a one step process.
Solution
#include <iostream>
using namespace std;
struct Node {
int data;
struct Node *next;
};
class Stack{
public:
Node *top;
int size;
Stack()
{
this->top = NULL;
this->size = 0;
}
void pop() {
if(this->top==NULL)
cout<<"Stack is empty"<<endl;
else {
cout<<"The popped element is "<< this->top->data <<endl;
this->top = this->top->next;
}
}
void display() {
43
struct Node* ptr;
if(this->top==NULL)
cout<<"stack is empty";
else {
ptr = this->top;
cout<<"Stack elements are: ";
while (ptr != NULL) {
cout<< ptr->data <<" ";
ptr = ptr->next;
}
}
cout<<endl;
}
};
int main() {
int ch, val;
Stack stack;
cout<<"1) Push in stack"<<endl;
cout<<"2) Pop from stack"<<endl;
cout<<"3) Display stack"<<endl;
cout<<"4) Exit"<<endl;
do {
cout<<"Enter choice: "<<endl;
cin>>ch;
switch(ch) {
case 1: {
cout<<"Enter value to be pushed:"<<endl;
cin>>val;
stack.push(val);
break;
}
case 2: {
stack.pop();
break;
}
case 3: {
stack.display();
break;
}
case 4: {
cout<<"Exit"<<endl;
break;
}
default: {
cout<<"Invalid Choice"<<endl;
}
}
}while(ch!=4);
return 0;
}
Output
1) Push in stack
2) Pop from stack
3) Display stack
4) Exit
Enter choice:
1
44
Enter value to be pushed:
11
Enter choice:
3
Stack elements are: 11
Enter choice:
2
The popped element is 11
Enter choice:
3
stack is empty
Enter choice:
45
5.1. What is a Queue Data Structure?
Queue is also an abstract data type or a linear data structure, just like stack data structures, in
which the first element is inserted from one end called the REAR(also called tail), and the
removal of existing element takes place from the other end called as FRONT(also called head).
This makes queue as FIFO(First in First Out) data structure, which means that element inserted
first will be removed first.
Which is exactly how queue system works in real world. If you go to a ticket counter to buy
movie tickets, and are first in the queue, then you will be the first one to get the tickets. Right?
Same is the case with Queue data structure. Data inserted first, will leave the queue first.
The process to add an element into queue is called Enqueue and the process of removal of an
element from queue is called Dequeue.
1. Like stack, queue is also an ordered list of elements of similar data types.
2. Queue is a FIFO( First in First Out ) structure.
3. Once a new element is inserted into the Queue, all the elements inserted before the
new element in the queue must be removed, to remove the new element.
4. peek( ) function is oftenly used to return the value of first element without
dequeuing it.
46
5.3. Applications of Queue
Queue, as the name suggests is used whenever we need to manage any group of objects in an
order in which the first one coming in, also gets out first while the others wait for their turn,
like in the following scenarios:
1. Serving requests on a single shared resource, like a printer, CPU task scheduling etc.
2. In real life scenario, Call Center phone systems uses Queues to hold people calling
them in an order, until a service representative is free.
3. Handling of interrupts in real-time systems. The interrupts are handled in the same
order as they arrive i.e First come first served.
Queue can be implemented using an Array, Stack or Linked List. The easiest way of
implementing a queue is by using an Array.
Initially the head(FRONT) and the tail(REAR) of the queue points at the first index of the
array (starting the index of array from 0). As we add elements to the queue, the tail keeps on
moving ahead, always pointing to the position where the next element will be inserted, while
the head remains at the first index.
47
When we remove an element from Queue, we can follow two possible approaches (mentioned
[A] and [B] in above diagram). In [A] approach, we remove the element at head position, and
then one by one shift all the other elements in forward position.
In approach [B] we remove the element from head position and then move head to the next
position.
In approach [A] there is an overhead of shifting the elements one position forward every
time we remove the first element.
In approach [B] there is no such overhead, but whenever we move head one position ahead,
after removal of first element, the size on Queue is reduced by one space each time.
48
5.4.1. Algorithm for ENQUEUE operation
Code
/* Below program is written in C++ language */
#include<iostream>
#define SIZE 10
class Queue
{
int a[SIZE];
int rear; //same as tail
int front; //same as head
public:
Queue()
{
rear = front = -1;
}
49
}
if( rear == SIZE-1)
{
cout << "Queue is full";
}
else
{
a[++rear] = x;
}
}
50
q.display();
return 0;
}
Output
1001
1002
1003
1004
To implement approach [A], you simply need to change the dequeue method, and include a
for loop which will shift all the remaining elements by one position.
Just like Stack, in case of a Queue too, we know exactly, on which position new element will
be added and from where an element will be removed, hence both these operations requires a
single step.
Enqueue: O(1)
Dequeue: O(1)
Size: O(1)
51
Please note that all operations (Enqueue, Dequeue and Front) must be implemented as
separate functions (methods).
Solution
#include<iostream>
#include<stdlib.h>
#include<cstring>
struct strng{
string d;
struct strng *next;
};
strng *input(){
strng *data;
data = new strng;
cin.ignore();
cout << "enter input : ";
getline(cin,data->d);
data->next = new strng;
return data;
}
for(entry=prev->next;entry!=NULL;entry=entry->next){
if(entry->next==top){
prev->next=entry->next;
delete(entry);
break;
}
prev=prev->next;
}
}
52
cout << bottom->d << endl;
}
void menu(){
cout << "\n\n\n\n\n";
cout << "1. enqueue\n";
cout << "2. dequeue\n";
cout << "3. show top\n";
cout << "4. show all\n";
cout << "0. terminate\n";
}
do{
menu();
cin >> command;
if(command==1){
system("CLS");
enqueue(entry,bottom);
}else if(command==2){
system("CLS");
dequeue(entry,bottom,top);
}else if(command==3){
system("CLS");
first(entry,bottom,top);
}else if(command==4){
system("CLS");
display(bottom);
}else if(command==0){
system("CLS");
}else{
system("CLS");
cout << "incorrect input";
}
}while(command!=0);
}
int main()
{
strng *top;
top = new strng;
top->next = NULL;
strng *bottom;
bottom = new strng;
bottom->next = top;
strng *entry;
entry = new strng;
simulation(top,bottom,entry);
return 0;
}
Output
53
1. enqueue
2. dequeue
3. show top
4. show all
0. terminate
Bibliography:
1. Data Structure Class Slides
54
6.1. Bubble Sort
Bubble sort is a sorting technique in which each pair of adjacent elements are compared, if they
are in wrong order we swap them. This algorithm is named as bubble sort because, same as
like bubbles the smaller or lighter elements comes up (at start) and bigger or heavier elements
goes down (at end). Below I have shared a program for bubble sort in C++ which sorts a list of
numbers in ascending order.
int main()
{
int a[50],n,i,j,temp;
cout<<"Enter the size of array: ";
cin>>n;
cout<<"Enter the array elements: ";
for(i=0;i<n;++i)
cin>>a[i];
for(i=1;i<n;++i)
{
for(j=0;j<(n-i);++j)
if(a[j]>a[j+1])
{
temp=a[j];
a[j]=a[j+1];
a[j+1]=temp;
}
}
return 0;
55
}
Output
Enter the size of array: 5
Enter the array elements: 4
7
1
0
8
Array after bubble sort: 0 1 4 7 8
56
int temp;
node *current=head;
node *traverse;
while(current!=NULL)
{
traverse=head;
while(traverse!=NULL)
{
if(current->data<traverse->data)
{
temp=current->data;
current->data=traverse->data;
traverse->data=temp;
}
traverse=traverse->next;
}
current=current->next;
}
}
void display()
{
node *temp=head;
while(temp!=NULL)
{
cout<<temp->data<<" ";
temp=temp->next;
}
}
int main()
{
putdata(3);
putdata(6);
putdata(9);
putdata(2);
putdata(1);
cout<<"before traversing"<<endl;
display();
cout<<"after traversing"<<endl;
sorting();
display();
57
}
Output
before traversing
3 6 9 2 1 after traversing
1 2 3 6 9
The selection sort algorithm sorts an array by repeatedly finding the minimum element
(considering ascending order) from unsorted part and putting it at the beginning. The algorithm
maintains two subarrays in a given array.
1) The subarray which is already sorted.
2) Remaining subarray which is unsorted.
In every iteration of selection sort, the minimum element (considering ascending order) from
the unsorted subarray is picked and moved to the sorted subarray.
Following example explains the above steps:
arr[] = 64 25 12 22 11
58
int temp = *xp;
*xp = *yp;
*yp = temp;
}
Output
Sorted array:
11 12 22 25 64
Insertion sort is a simple sorting algorithm that works the way we sort playing cards in our
hands. in other words we sort cards using insertion sort mechanism. For this technique, we pick
up one element from the data set and shift the data elements to make a place to insert back the
picked up element into the data set.
59
6.3.1. The complexity of Insertion Sort Technique
Time Complexity: O(n) for best case, O(n2) for average and worst case
Space Complexity: O(1)
6.3.2. Algorithm
insertionSort(array, size)
Input: An array of data, and the total number in the array
Output: The sorted Array
Begin
for i := 1 to size-1 do
key := array[i]
j := i
while j > 0 AND array[j-1] > key do
array[j] := array[j-1];
j := j – 1
done
array[j] := key
done
End
Example:
60
Example 6-4 Insertion Sort Implementation
Code
#include <bits/stdc++.h>
using namespace std;
/* Driver code */
int main()
{
int arr[] = { 12, 11, 13, 5, 6 };
int n = sizeof(arr) / sizeof(arr[0]);
insertionSort(arr, n);
printArray(arr, n);
return 0;
}
Output
5 6 11 12 13
61
6.4. Example Problem
Solution
#include<iostream>
#include<cstdlib>
#include<cstring>
using namespace std;
struct Book{
int id;
string name;
};
int swapped = 0;
62
} //end inner for
}
int swapped = 0;
int main()
{
const int N = 5;
Book A[N];
A[0].id = 10;
A[0].name = "Book D";
A[1].id = 5;
A[1].name = "Book E";
A[2].id = 1;
A[2].name = "Book B";
A[3].id = 3;
A[3].name = "Book C";
A[4].id = 2;
A[4].name = "Book A";
bubbleSortById(A, N);
cout<<endl<<endl;
cout<<"After Bubble Sort by Id"<<endl;
printArray(A, N);
bubbleSortByName(A, N);
cout<<endl<<endl;
cout<<"After Bubble Sort by Name"<<endl;
printArray(A, N);
return 0;
}
63
Output
10 Book D
5 Book E
1 Book B
3 Book C
2 Book A
64
7.1. Merge Sort
Like QuickSort, Merge Sort is a Divide and Conquer algorithm. It divides input array in two
halves, calls itself for the two halves and then merges the two sorted halves. The merge()
function is used for merging two halves. The merge(arr, l, m, r) is key process that assumes
that arr[l..m] and arr[m+1..r] are sorted and merges the two sorted sub-arrays into one. See
following C implementation for details.
MergeSort(arr[], l, r)
If r > l
1. Find the middle point to divide the array into two halves:
middle m = (l+r)/2
2. Call mergeSort for first half:
Call mergeSort(arr, l, m)
3. Call mergeSort for second half:
Call mergeSort(arr, m+1, r)
4. Merge the two halves sorted in step 2 and 3:
Call merge(arr, l, m, r)
The following diagram shows the complete merge sort process for an example array {38, 27,
43, 3, 9, 82, 10}. If we take a closer look at the diagram, we can see that the array is recursively
divided in two halves till the size becomes 1. Once the size becomes 1, the merge processes
comes into action and starts merging arrays back till the complete array is merged.
65
Example 7-1 Merge Sort Implementation
Code
/* C++ program for Merge Sort */
#include<cstdlib>
#include<iostream>
using namespace std;
66
if (L[i] <= R[j])
{
arr[k] = L[i];
i++;
}
else
{
arr[k] = R[j];
j++;
}
k++;
}
merge(arr, l, m, r);
}
}
/* UTILITY FUNCTIONS */
/* Function to print an array */
void printArray(int A[], int size)
{
int i;
for (i=0; i < size; i++)
cout<<A[i]<<" ";
cout<<endl;
}
67
/* Driver program to test above functions */
int main()
{
int arr[] = {12, 11, 13, 5, 6, 7};
int arr_size = sizeof(arr)/sizeof(arr[0]);
Sorted array is
5 6 7 11 12 13
Sorting arrays on different machines. Merge Sort is a recursive algorithm and time complexity
can be expressed as following recurrence relation.
The above recurrence can be solved either using Recurrence Tree method or Master method. It
falls in case II of Master Method and solution of the recurrence is \Theta(nLogn).
Time complexity of Merge Sort is \Theta(nLogn) in all 3 cases (worst, average and best) as
merge sort always divides the array into two halves and take linear time to merge two halves.
Merge Sort is useful for sorting linked lists in O(nLogn) time.In the case of linked lists,
the case is different mainly due to the difference in memory allocation of arrays and
linked lists. Unlike arrays, linked list nodes may not be adjacent in memory. Unlike an
array, in the linked list, we can insert items in the middle in O(1) extra space and O(1)
time. Therefore merge operation of merge sort can be implemented without extra space
for linked lists. In arrays, we can do random access as elements are contiguous in
memory. Let us say we have an integer (4-byte) array A and let the address of A[0] be
68
x then to access A[i], we can directly access the memory at (x + i*4). Unlike arrays,
we can not do random access in the linked list. Quick Sort requires a lot of this kind of
access. In linked list to access i’th index, we have to travel each and every node from
the head to i’th node as we don’t have a continuous block of memory. Therefore, the
overhead increases for quicksort. Merge sort accesses data sequentially and the need
of random access is low.
Inversion Count Problem
Used in External Sorting
7.2. QuickSort
Like Merge Sort, QuickSort is a Divide and Conquer algorithm. It picks an element as pivot
and partitions the given array around the picked pivot. There are many different versions of
quickSort that pick pivot in different ways.
Always pick first element as pivot.
Always pick last element as pivot (implemented below)
Pick a random element as pivot.
Pick median as pivot.
The key process in quickSort is partition(). Target of partitions is, given an array and an element
x of array as pivot, put x at its correct position in sorted array and put all smaller elements
(smaller than x) before x, and put all greater elements (greater than x) after x. All this should
be done in linear time.
69
Partition Algorithm
There can be many ways to do partition, following pseudo code adopts the method given in
CLRS book. The logic is simple, we start from the leftmost element and keep track of index
of smaller (or equal to) elements as i. While traversing, if we find a smaller element, we swap
current element with arr[i]. Otherwise we ignore current element.
70
i = (low - 1) // Index of smaller element
71
Example 7-2 Quick Sort Implementation
Code
/* C++ implementation of QuickSort */
#include <bits/stdc++.h>
using namespace std;
// Driver Code
int main()
{
int arr[] = {10, 7, 8, 9, 1, 5};
int n = sizeof(arr) / sizeof(arr[0]);
quickSort(arr, 0, n - 1);
cout << "Sorted array: \n";
printArray(arr, n);
return 0;
}
Output
Sorted array:
1 5 7 8 9 10
The first two terms are for two recursive calls, the last term is for the partition process. k is the
number of elements which are smaller than pivot.
The time taken by QuickSort depends upon the input array and partition strategy. Following
are three cases.
Worst Case: The worst case occurs when the partition process always picks greatest or
smallest element as pivot. If we consider above partition strategy where last element is always
picked as pivot, the worst case would occur when the array is already sorted in increasing or
decreasing order. Following is recurrence for worst case.
73
Best Case: The best case occurs when the partition process always picks the middle element
as pivot. Following is recurrence for best case.
The solution of above recurrence is \theta(nLogn). It can be solved using case 2 of Master
Theorem.
Average Case:
To do average case analysis, we need to consider all possible permutation of array and calculate
time taken by every permutation which doesn’t look easy.
We can get an idea of average case by considering the case when partition puts O(n/9) elements
in one set and O(9n/10) elements in other set. Following is recurrence for this case.
Although the worst case time complexity of QuickSort is O(n2) which is more than many other
sorting algorithms like Merge Sort and Heap Sort, QuickSort is faster in practice, because its
inner loop can be efficiently implemented on most architectures, and in most real-world data.
QuickSort can be implemented in different ways by changing the choice of pivot, so that the
worst case rarely occurs for a given type of data. However, merge sort is generally considered
better when data is huge and stored in external storage.
Solution
#include<iostream>
#include<string>
using namespace std;
struct list{
int id;
string name;
string getID();
74
};
list t = arr[i+1];
arr[i+1] = arr[high];
arr[high] = t;
int pi = i + 1;
cout<<"Shelf\n\n";
cout<<"ID | Name\n";
cout<<"------------------------\n";
for(int i = 0;i < size;i++){
cout<<arr[i].id<<" | ";
cout<<arr[i].name;
cout<<"\n------------------------\n";
}
int j = right;
int i = left - 1;
75
while (true)
{
// cout<<"running"<<endl;
while (arr[++i].name < val);
if(i >= j)
break;
temp=arr[i];
arr[i]=arr[j];
arr[j]=temp;
}
temp=arr[i];
arr[i]=arr[right];
arr[right]=temp;
int pi = i;
int main()
{
list *arr = new list[6];
arr[0].id=4;
arr[1].id=7;
arr[2].id=8;
arr[3].id=9;
arr[4].id=1;
arr[5].id=5;
int n = 6;
cout<<"\n***BEFORE SORT***\n"<<endl;
printArray(arr,6);
int option;
cout<<"choose\n1) Sort by ID\n2)Sort by name\n";
cin>>option;
76
switch (option)
{
case 1: {
quickSort_id(arr, 0, n-1);
cout<<"\nSorted array by ID: \n"<<endl;
printArray(arr, 6);
break;
}
case 2: {
quickSort_name(arr, 0, n-1);
cout<<"\nSorted array by name: \n"<<endl;
printArray(arr, 6);
break;
}
}
}
Output
***BEFORE SORT***
Shelf
ID | Name
------------------------
4 | Living To Tell
------------------------
7 | Eleven Minute
------------------------
8 | The Present
------------------------
9 | The Namesake
------------------------
1 | Go-Getter
------------------------
5 | Flash Boys
------------------------
choose
1) Sort by ID
2)Sort by name
77
8.1. Binary Tree
A binary tree is a hierarchical data structure whose behavior is similar to a tree, as it contains
root and leaves (a node that has no child). The root of a binary tree is the topmost node. Each
node can have at most two children, which are referred to as the left child and the right child.
A node that has at least one child becomes a parent of its child. A node that has no child is
a leaf. In this tutorial, you will be learning about the Binary tree data structures, its principles,
and strategies in applying this data structures to various applications.
From the preceding binary tree diagram, we can conclude the following:
The root of the tree is the node of element 1 since it’s the topmost node
The children of element 1 are element 2 and element 3
The parent of elements 2 and 3 is 1
There are four leaves in the tree, and they are element 4, element 5, element 6, and
element 7 since they have no child
This hierarchical data structure is usually used to store information that forms a hierarchy, such
as a file system of a computer.
A binary search tree (BST) is a sorted binary tree, where we can easily search for any key
using the binary search algorithm. To sort the BST, it has to have the following properties:
The node’s left subtree contains only a key that’s smaller than the node’s key
The node’s right subtree contains only a key that’s greater than the node’s key
You cannot duplicate the node’s key value
By having the preceding properties, we can easily search for a key value as well as find the
maximum or minimum key value. Suppose we have the following BST:
78
As we can see in the preceding tree diagram, it has been sorted since all of the keys in the
root’s left subtree are smaller than the root’s key, and all of the keys in the root’s right
subtree are greater than the root’s key. The preceding BST is a balanced BST since it has a
balanced left and right subtree. We also can define the preceding BST as a balanced BST
since both the left and right subtrees have an equal height (we are going to discuss this further
in the upcoming section).
However, since we have to put the greater new key in the right subtree and the smaller new
key in the left subtree, we might find an unbalanced BST, called a skewed left or a skewed
right BST. Please see the following diagram:
The preceding image is a sample of a skewed left BST, since there’s no right subtree. Also, we
can find a BST that has no left subtree, which is called a skewed right BST, as shown in the
following diagram:
79
As we can see in the two skewed BST diagrams, the height of the BST becomes taller since
the height equals to N – 1 (where N is the total keys in the BST), which is five. Comparing this
with the balanced BST, the root’s height is only three.
To create a BST in C++, we need to modify our TreeNode class in the preceding binary tree
discussion, Building a binary tree ADT. We need to add the Parent properties so that we can
track the parent of each node. It will make things easier for us when we traverse the tree. The
class should be as follows:
class BSTNode
{
public:
int Key;
BSTNode * Left;
BSTNode * Right;
BSTNode * Parent;
};
There are several basic operations which BST usually has, and they are as follows:
Insert() is used to add a new node to the current BST. If it’s the first time we have
added a node, the node we inserted will be a root node.
PrintTreeInOrder() is used to print all of the keys in the BST, sorted from the
smallest key to the greatest key.
Search() is used to find a given key in the BST. If the key exists it
returns TRUE, otherwise it returns FALSE.
FindMin() and FindMax() are used to find the minimum key and the maximum key
that exist in the BST.
Successor() and Predecessor() are used to find the successor and predecessor of a
given key. We are going to discuss these later in the upcoming section.
Remove() is used to remove a given key from BST.
80
8.2.1. Inserting a new key into a BST
Inserting a key into the BST is actually adding a new node based on the behavior of the BST.
Each time we want to insert a key, we have to compare it with the root node (if there’s no
root beforehand, the inserted key becomes a root) and check whether it’s smaller or greater
than the root’s key. If the given key is greater than the currently selected node’s key, then go
to the right subtree. Otherwise, go to the left subtree if the given key is smaller than the
currently selected node’s key. Keep checking this until there’s a node with no child so that we
can add a new node there.
Insertion of elements into the BST is a reflection of the definition of BST. If there is no tree
already, the insert creates a one node tree. Remember that each sub-tree of a node is a binary
tree itself. If the tree is exists, then we proceed like this:
1. If the tree or (sub-tree) does not exist, create a node and insert the value in there.
2. If the value we are inserting is less than the root of the tree (or sub-tree) we move to
the left sub-tree and start at step 1 again
3. If the value is greater than the root of the tree (or sub-tree) we move to the right sub-
tree and start at step 1 again
4. If the value is equal to the root of the tree or (sub-tree) we do nothing because the
value is already there! In this case we return.
81
The left and right are pointers to the corresponding children of the node.
There are three ways to traverse a binary search tree and a simple binary tree.
1. Pre-order traversal
2. In-order traversal
3. Post-order traversal
Pre-order traversal
In the pre-order traversal the parent node is visited before its children. In this type of traversal
the parent node is visited followed by visiting its left sub-tree and right sub-tree. For example
pre-order traversal of the tree given above will be:
8, 3, 1, 6, 4, 7, 10, 14, 13
In-order traversal
In the in-order traversal, the left sub-tree is visited first, then the node itself is visited followed
by visiting the right sub-tree. In-order traversal of the above tree will be like:
1, 3, 4, 6, 7, 8, 10, 13,14
inOrderTraverse (root->right);
}
Post-order traversal
82
In the post-order traversal, first the left sub-tree of a node is visited, then the right sub-tree is
visited and in the end the node itself is visited. Following is the post-order traversal of the
above tree.
struct node
{
int key;
struct node *left, *right;
};
83
/* Otherwise, recur down the tree */
if (key < node->key)
node->left = insert(node->left, key);
else if (key > node->key)
node->right = insert(node->right, key);
return 0;
}
Output
20
30
40
50
60
70
80
Solution
#include<iostream>
using namespace std;
84
struct TreeNode
{
int value;
TreeNode *left;
TreeNode *right;
TreeNode(){
this->left = NULL;
this->right =NULL;
}
};
class BST
{
private:
TreeNode *root;
if(nodePtr == NULL)
cout<<"Cannot delete empty node.\n";
tempNodePtr = nodePtr;
//reattch the right subtree
85
nodePtr = nodePtr->right;
delete tempNodePtr;
}
}
void displayInOrder(TreeNode *nodePtr)
{
if(nodePtr)
{
displayInOrder(nodePtr->left);
cout<<nodePtr->value<<endl;
displayInOrder(nodePtr->right);
}
}
void displayPreOrder(TreeNode *nodePtr)
{
if(nodePtr)
{
cout<<nodePtr->value<<endl;
displayPreOrder(nodePtr->left);
displayPreOrder(nodePtr->right);
}
}
public:
BST()
{
root = NULL;
}
~BST()
{
// destroySubTree(root);
}
newNode->value = num;
if(root == NULL) //is the tree empty?
{
root = newNode;
}
else
{
nodePtr = root;
86
while(nodePtr != NULL)
{
if(num < nodePtr->value)
{
if(nodePtr ->left != NULL)
nodePtr = nodePtr->left;
else
{
nodePtr->left = newNode;
break;
}
}
else if(num > nodePtr->value)
{
if(nodePtr->right)
nodePtr = nodePtr->right;
else
{
nodePtr->right = newNode;
break;
}
}
else
{
cout<<"Duplicate value found in
tree.\n";
break;
}
}
}
}
while(nodePtr)
{
if(nodePtr->value == num)
return true;
else if(num < nodePtr->value)
nodePtr = nodePtr->left;
else
nodePtr = nodePtr->right;
}
return false;
}
//DELETION FUNCTION
void remove(int num)
{
deleteNode(num, root);
}
//TRAVERSING FUNCTIONS
87
void showNodesInOrder(void){
displayInOrder(root);
}
void showNodesPreOrder(void){
displayPreOrder(root);
}
void showNodesPostOrder(void){
displayPostOrder(root);
}
};
int main()
{
BST tree;
cout<<"Inseting nodes..";
tree.insertNode(5);
tree.insertNode(8);
tree.insertNode(3);
tree.insertNode(12);
tree.insertNode(9);
tree.insertNode(7);
cout<<endl<<"Insertion done:";
if(tree.searchNode(3))
cout<<"\n3 is found in the tree.\n";
else
cout<<"3 is not found in the tree.\n";
cout<<"Deleting 8...\n";
tree.remove(8);
cout<<"Deleting 12...\n";
tree.remove(12);
PreOrder traversal:
5
3
88
8
7
12
9
Postorder traversal:
3
7
9
12
8
5
89
9.1. Introduction
AVL tree is a self-balancing Binary Search Tree (BST) where the difference between heights
of left and right subtrees cannot be more than one for all nodes.
An Example Tree that is an AVL Tree
The above tree is AVL because differences between heights of left and right subtrees for
every node is less than or equal to 1.
An Example Tree that is NOT an AVL Tree
90
The above tree is not AVL because differences between heights of left and right subtrees for
8 and 18 is greater than 1.
Most of the BST operations (e.g., search, max, min, insert, delete.. etc) take O(h) time where h
is the height of the BST. The cost of these operations may become O(n) for a skewed Binary
tree. If we make sure that height of the tree remains O(Logn) after every insertion and deletion,
then we can guarantee an upper bound of O(Logn) for all these operations. The height of an
AVL tree is always O(Logn) where n is the number of nodes in the tree.
9.1.2. Insertion
To make sure that the given tree remains AVL after every insertion, we must augment the
standard BST insert operation to perform some re-balancing. Following are two basic
operations that can be performed to re-balance a BST without violating the BST property
(keys(left) < key(root) < keys(right)).
1) Left Rotation
2) Right Rotation
T1, T2 and T3 are subtrees of the tree
rooted with y (on the left side) or x (on
the right side)
y x
/ \ Right Rotation / \
x T3 - - - - - - - > T1 y
/ \ < - - - - - - - / \
T1 T2 Left Rotation T2 T3
Keys in both of the above trees follow the
following order
keys(T1) < key(x) < keys(T2) < key(y) < keys(T3)
So BST property is not violated anywhere.
91
Steps to follow for insertion
Let the newly inserted node be w
1) Perform standard BST insert for w.
2) Starting from w, travel up and find the first unbalanced node. Let z be the first unbalanced
node, y be the child of z that comes on the path from w to z and x be the grandchild of z that
comes on the path from w to z.
3) Re-balance the tree by performing appropriate rotations on the subtree rooted with z. There
can be 4 possible cases that needs to be handled as x, y and z can be arranged in 4 ways.
Following are the operations to be performed in above mentioned 4 cases. In all of the cases,
we only need to re-balance the subtree rooted with z and the complete tree becomes balanced
as the height of subtree (After appropriate rotations) rooted with z becomes same as it was
before insertion.
92
z y
/ \ / \
T1 y Left Rotate(z) z x
/ \ - - - - - - - -> /\ /\
T2 x T1 T2 T3 T4
/ \
T3 T4
Insertion Examples:
93
94
9.1.3. Implementation
Following is the implementation for AVL Tree Insertion. The following implementation uses
the recursive BST insert to insert a new node. In the recursive BST insert, after insertion, we
get pointers to all ancestors one by one in a bottom-up manner. So we don’t need parent
pointer to travel up. The recursive code itself travels up and visits all the ancestors of the
newly inserted node.
1) Perform the normal BST insertion.
95
2) The current node must be one of the ancestors of the newly inserted node. Update the
height of the current node.
3) Get the balance factor (left subtree height – right subtree height) of the current node.
4) If balance factor is greater than 1, then the current node is unbalanced and we are either in
Left Left case or left Right case. To check whether it is left left case or not, compare the
newly inserted key with the key in left subtree root.
5) If balance factor is less than -1, then the current node is unbalanced and we are either in
Right Right case or Right-Left case. To check whether it is Right Right case or not, compare
the newly inserted key with the key in right subtree root.
96
node->left = NULL;
node->right = NULL;
node->height = 1; // new node is initially
// added at leaf
return(node);
}
// Perform rotation
x->right = y;
y->left = T2;
// Update heights
y->height = max(height(y->left),
height(y->right)) + 1;
x->height = max(height(x->left),
height(x->right)) + 1;
// Perform rotation
y->left = x;
x->right = T2;
// Update heights
x->height = max(height(x->left),
height(x->right)) + 1;
y->height = max(height(y->left),
height(y->right)) + 1;
97
// Recursive function to insert a key
// in the subtree rooted with node and
// returns the new root of the subtree.
Node* insert(Node* node, int key)
{
/* 1. Perform the normal BST insertion */
if (node == NULL)
return(newNode(key));
98
// of every node
void preOrder(Node *root)
{
if(root != NULL)
{
cout << root->key << " ";
preOrder(root->left);
preOrder(root->right);
}
}
// Driver Code
int main()
{
Node *root = NULL;
return 0;
}
Output
Preorder traversal of the constructed AVL tree is
30 20 10 25 40 50
99
The tree must be developed on registration number attribute. In order traversal of an AVL
tree shows the data in sorted order. Implement the in-order traversal.
Solution
#include<iostream>
using namespace std;
struct student{
int reg;
string name;
float gpa;
student *left;
student *right;
}*root;
left = height(temp->left);
right = height(temp->right);
difference = left-right;
return difference;
}
101
else{
cout<<"\nThe tree is balance"<<endl;
return temp;
}
else{
if(val < root->reg){
cout<<"\nInserting\n";
root->left = insert (root->left, val);
cout<<"Check Balance\n";
root = balance(root);
return root;
}
else{
cout<<"\nInserting\n";
root->right = insert (root->right, val);
cout<<"Check Balance\n";
root = balance(root);
return root;
}
}
}
int main(){
int choice;
while(choice !=5){
cout<<"\n1. Enter student in tree"<<endl;
cout<<"2. Inorder Traversal of tree"<<endl;
cout<<"3. Display"<<endl;
cout<<"4. Height of tree"<<endl;
cin>>choice;
cout<<endl;
switch(choice){
102
case 1:
int val;
cout<<"Enter reg number of student to
insert"<<endl;
cin>>val;
root = insert(root, val);
break;
case 2:
cout<<"Inorder traversal is: "<<endl;
inorder(root);
cout<<endl;
break;
case 3:
display(root, 1);
break;
case 4:
int he = height(root);
cout<<"The height of tree is: "<<he<<endl;
break;
}
}
}
Output
1. Enter student in tree
2. Inorder Traversal of tree
3. Display
4. Height of tree
103
10.1. Introduction
Graph is a data structure. A graph is a pair of sets (V, E), where V is the set of vertices and E is
the set of edges, connecting the pairs of vertices.
An undirected graph is graph, i.e., a set of objects (called vertices or nodes) that are connected,
where all the edges are bidirectional.
The graph given above is an undirected graph. We can say that if the vertices represent cities
then the edges as roads connecting those cities. Two-way roads can be represented as
undirected graphs.
104
The graph given above is a directed graph. We can say that if the vertices represent cities then
the edges as roads connecting those cities. One-way roads can be represented as directed graphs
where arrow points the direction of traffic movement.
105
10.3.2. Adjacency list representation
If the graph is not dense, in other words, if the graph is sparse, a better solution to represent
matrix is an adjacency list representation. Adjacency lists are the standard way to represent
graphs. For each vertex, we keep a list of all adjacent vertices. If the edges have weights, then
this additional information is also stored in the cells. The space requirement is then O(|E| +
|V|). Undirected graphs can be similarly represented; each edge (u, v) appears in two lists, so
the space usage essentially doubles. A common requirement in graph algorithms is to find all
vertices adjacent to some given vertex v, this can be done, in time proportional to the number
of such vertices found, by a simple scan down the appropriate adjacency list.
106
10.4. Graph Operations
10.4.1. Append a new node
107
AdjListNode *newAdjListNode(int);
Graph *createGraph(int);
void addEdge(Graph*,int,int);
void printGraph(Graph*);
int main(){
//create a new graph
int totalVertices=4;
Graph *graph;
graph=createGraph(totalVertices);
//connect edges
addEdge(graph,0,1);
addEdge(graph,0,2);
addEdge(graph,0,3);
addEdge(graph,1,3);
addEdge(graph,2,3);
/*
addEdge(graph,0,1);
addEdge(graph,0,4);
addEdge(graph,1,2);
addEdge(graph,1,3);
addEdge(graph,1,4);
addEdge(graph,2,3);
addEdge(graph,3,4);
*/
//print the adjacency list representation of graph
printGraph(graph);
}
108
graph->arr[dest].head=nptr;
}
Graph traversal refers to the problem of visiting all the nodes in a graph in a particular manner.
Tree traversal is a special case of graph traversal. In contrast to tree traversal, in general:
In graph traversal, each node may have to be visited more than once,
A root-like node that connects to all other nodes might not exist
In graph theory, breadth-first search (BFS) is a graph search algorithm that begins at the root
node and explores all the neighboring nodes. Then for each of those nearest nodes, it explores
their unexplored neighbor nodes, and so on, until it fulfills its objective (i.e., visit all the nodes
or reach the destination node, if any). From the standpoint of the algorithm, all child nodes
obtained by expanding a node are added to a FIFO (i.e., First In, First Out) queue. In typical
implementations, nodes that have not yet been examined for their neighbors are placed in some
container (such as a queue or linked list) and have special color (such as white) or status (such
as unprocessed, not visited etc) then once examined are placed in another container and marked
as processed, visited and etc.
BFS Algorithm
109
1. Enqueue the root node.
2. Dequeue a node and examine it.
3. If the element sought is found in this node, quit the search and
return a result.
4. Otherwise enqueue any successors (the direct child nodes) that have
not yet been discovered.
5. If the queue is empty, every node on the graph has been examined –
quit the search and return "not found".
6. If the queue is not empty, repeat from Step 2.
Formally, DFS is an uninformed search that progresses by expanding the first child node of the
search tree (or graph) that appears, and thus going deeper and deeper until a goal node is found,
or until it hits a node that has no children. Then the search backtracks, returning to the most
recent node it hasn't finished exploring. In a non-recursive implementation, all freshly
expanded nodes are added to a stack for exploration.
DFS Pseudocode
procedure DFS(G, s)
for each vertex u in G
u.status = notVisited/white
u.pi = NULL
time = 0;
for each vertex u in G
if u.status = notVisited/white
DFS_visit(G, u)
procedure DFS_visit(G, u)
u.status = visited / grey
time = time + 1;
u.d = time;
for each v adjacent to u do
if v.status = notVisited/white
v.pi = u;
DFS_visit(G, v)
u.status = processed/ black
Statement
110
Implement the graph given in the above figure in C++ and show depth first search traversal.
Code
// C++ program to print DFS traversal from
// a given vertex in a given graph
#include<iostream>
#include<list>
using namespace std;
Graph::Graph(int V)
{
this->V = V;
adj = new list<int>[V];
}
111
void Graph::DFSUtil(int v, bool visited[])
{
// Mark the current node as visited and
// print it
visited[v] = true;
cout << v << " ";
// Driver code
int main()
{
// Create a graph given in the above diagram
Graph g(4);
g.addEdge(0, 1);
g.addEdge(0, 2);
g.addEdge(1, 2);
g.addEdge(2, 0);
g.addEdge(2, 3);
g.addEdge(3, 3);
return 0;
}
Output
Following is Depth First Traversal (starting from vertex 2)
2 0 1 3
Bibliography:
1. Data Structure Class Slides
112
11.1. Introduction
Searching is dominant operation on any data structure. Most of the cases for inserting, deleting,
and updating all operations require searchin. So, search operation of a data structure determines
it’s time complexity. If we take any data structure the best time complexity for searching is O
(log n) in AVL trees. In most of the cases it will take O (n) time. To solve this searching
problem hashing concept is introduced that takes O (1) time for searching.
In hashing, large keys are converted into small keys by using hash functions. The values are
then stored in a data structure called hash table. The idea of hashing is to distribute entries
(key/value pairs) uniformly across an array. Each element is assigned a key (converted key).
By using that key you can access the element in O(1) time. Using the key, the algorithm (hash
function) computes an index that suggests where an entry can be found or inserted.
11.2. Implementation
1 An element is converted into an integer by using a hash function. This element can be
used as an index to store the original element, which falls into the hash table.
2 The element is stored in the hash table where it can be quickly retrieved using hashed
key.
hash = hashfunc(key)
index = hash % array_size
In this method, the hash is independent of the array size and it is then reduced to an index (a
number between 0 and array_size − 1) by using the modulo operator (%).
A hash function is any function that can be used to map a data set of an arbitrary size to a data
set of a fixed size, which falls into the hash table. The values returned by a hash function are
called hash values, hash codes, hash sums, or simply hashes. To achieve a good hashing
mechanism, it is important to have a good hash function with the following basic requirements:
1. Easy to compute: It should be easy to compute and must not become an algorithm.
2. Uniform distribution: It should provide a uniform distribution across the hash table and
should not result in clustering.
113
3. Less collisions: Collisions occur when pairs of elements are mapped to the same hash
value. These should be avoided.
Note: Irrespective of how good a hash function is, collisions are bound to occur. Therefore,
to maintain the performance of a hash table, it is important to manage collisions through
various collision resolution techniques.
A hash table is a data structure that is used to store keys/value pairs. It uses a hash function to
compute an index into an array in which an element will be inserted or searched. By using a
good hash function, hashing can work well. Under reasonable assumptions, the average time
required to search for an element in a hash table is O(1).
Let us consider string S. You are required to count the frequency of all the characters in this
string.
string S = “ababcd”
The simplest way to do this is to iterate over all the possible characters and count their
frequency one by one. The time complexity of this approach is O(26*N) where N is the size of
the string and there are 26 possible characters.
Let us apply hashing to this problem. Take an array frequency of size 26 and hash the 26
characters with indices of the array by using the hash function. Then, iterate over the string and
increase the value in the frequency at the corresponding index for each character. The
complexity of this approach is O(N) where N is the size of the string.
int Frequency[26];
int hashFunc(char c)
{
return (c - ‘a’);
}
void countFre(string S)
{
for(int i = 0;i < S.length();++i)
{
int index = hashFunc(S[i]);
Frequency[index]++;
}
for(int i = 0;i < 26;++i)
cout << (char)(i+’a’) << ‘ ‘ << Frequency[i] << endl;
}
114
11.3. Example Problem
Statement
Implement a menu driven program to insert, search and delete elements in a hash table using
C++.
Code
#include<iostream>
#include<cstdlib>
#include<string>
#include<cstdio>
using namespace std;
const int T_S = 200;
class HashTableEntry {
public:
int k;
int v;
HashTableEntry(int k, int v) {
this->k= k;
this->v = v;
}
};
class HashMapTable {
private:
HashTableEntry **t;
public:
HashMapTable() {
t = new HashTableEntry * [T_S];
for (int i = 0; i< T_S; i++) {
t[i] = NULL;
}
}
int HashFunc(int k) {
return k % T_S;
}
void Insert(int k, int v) {
int h = HashFunc(k);
while (t[h] != NULL && t[h]->k != k) {
h = HashFunc(h + 1);
}
if (t[h] != NULL)
delete t[h];
t[h] = new HashTableEntry(k, v);
}
int SearchKey(int k) {
int h = HashFunc(k);
while (t[h] != NULL && t[h]->k != k) {
h = HashFunc(h + 1);
}
if (t[h] == NULL)
return -1;
else
return t[h]->v;
}
void Remove(int k) {
int h = HashFunc(k);
115
while (t[h] != NULL) {
if (t[h]->k == k)
break;
h = HashFunc(h + 1);
}
if (t[h] == NULL) {
cout<<"No Element found at key "<<k<<endl;
return;
} else {
delete t[h];
}
cout<<"Element Deleted"<<endl;
}
~HashMapTable() {
for (int i = 0; i < T_S; i++) {
if (t[i] != NULL)
delete t[i];
delete[] t;
}
}
};
int main() {
HashMapTable hash;
int k, v;
int c;
while (1) {
cout<<"1.Insert element into the table"<<endl;
cout<<"2.Search element from the key"<<endl;
cout<<"3.Delete element at a key"<<endl;
cout<<"4.Exit"<<endl;
cout<<"Enter your choice: ";
cin>>c;
switch(c) {
case 1:
cout<<"Enter element to be inserted: ";
cin>>v;
cout<<"Enter key at which element to be inserted: ";
cin>>k;
hash.Insert(k, v);
break;
case 2:
cout<<"Enter key of the element to be searched: ";
cin>>k;
if (hash.SearchKey(k) == -1) {
cout<<"No element found at key "<<k<<endl;
continue;
} else {
cout<<"Element at key "<<k<<" : ";
cout<<hash.SearchKey(k)<<endl;
}
break;
case 3:
cout<<"Enter key of the element to be deleted: ";
cin>>k;
hash.Remove(k);
break;
case 4:
exit(1);
default:
cout<<"\nEnter correct option\n";
}
116
}
return 0;
}
Output
1.Insert element into the table
2.Search element from the key
3.Delete element at a key
4.Exit
Enter your choice: 1
Enter element to be inserted: 10
117
In this lab, your instructor will give you one or more problems. There could be multiple possible
solutions to that problem. You will be required to solve those problems in your lab without
consultation with your instructor (and fellows).
12.1. Rubric
Rubric may vary according to the problem statement. A tentative rubric for the open-ended lab
is as follows:
General Rubric1:
Running: 2
Runs without exception: 1
Interactive menu: 0.5
Displays proper messages/operations clearly: 0.5
Completeness: 3
Sub task 1: 1
Sub task 2: 1
Sub task 3: 1
Accuracy: 4
Appropriate data structure: 1
Efficient (Computational Complexity) algorithms: 1
Passes all test cases: 1
Viva: 1
Clarity 1 (No credit, if not relevant implementation):
Indentation: 0.5
Meaningful/relevant variable/function names: 0.5
1
This is a general rubric, actual rubric may vary in every lab according to the contents
118
Example 12-1 Open Ended Lab Sample Problem
119
Problem Statement (Maximum Time: 3 hours)
Design and implement an efficient C++ program for a general store management system.
The program must have the option to display list of items along with price and available
quantities. A user should be able to add items to the cart, remove items from the cart and
display detailed bill (sorted by item name). Please note that whenever a user adds an item to
the cart the quantity of that item reduces from the store.
Rubric (10 Points)
- Running: 3
* Runs without exception: 1
* Interactive menu: 1
* Displays proper messages/operations clearly: 1
- Completeness: 3
* Display items: 0.5
* Add to cart: 1
* Remove from cart: 0.5
* Display bill: 0.5
* Sorted by name: 0.5
- Accuracy: 3
* Apropirate data structure: 1
* Efficient (Computational Complexity) algorithms: 1
* Passes all test cases: 1
120
#include<iostream>
#include<cstring>
using namespace std;
struct items
{
string item_name;
float price;
int Quantity;
items *next;
};
struct cart
{
string name_of_product;
float price_of_product;
int quantity_of_product;
cart *next;
}*chead=NULL;
if (chead==NULL)
{
chead = new cart;
chead->name_of_product=name_of_item;
chead->quantity_of_product=quantity;
chead->price_of_product=find_price(head,name_of_item);
chead->next=NULL;
for(items *ptr=head; ptr!=NULL; ptr=ptr->next)
{
if (ptr->item_name==name_of_item)
{
ptr->Quantity=(ptr->Quantity) - quantity;
}
}
cout<<"Item added to cart!"<<endl;
}
else
{
cart *ptr1=chead;
for(; ptr1->next!=NULL; ptr1=ptr1->next){}
cart *newnode = new cart;
newnode->name_of_product=name_of_item;
newnode->quantity_of_product=quantity;
newnode->price_of_product=find_price(head,name_of_item);
ptr1->next=newnode;
newnode->next=NULL;
121
for(items *ptr=head; ptr!=NULL; ptr=ptr->next)
{
if (ptr->item_name==name_of_item)
{
ptr->Quantity=(ptr->Quantity) - quantity;
}
}
cout<<"Item added to cart!"<<endl;
}
}
void Display_cart()
{
float total_bill=0;
for(cart *ptr=chead; ptr!=NULL; ptr=ptr->next)
{
cout<<"Item: "<<ptr->name_of_product<<"\t"<<"Quantity: "<<ptr-
>quantity_of_product<<"\t"<<"Price: "<<(ptr->quantity_of_product)*(ptr-
>price_of_product);
total_bill+=(ptr->quantity_of_product)*(ptr->price_of_product);
cout<<endl;
}
cout<<"Your total bill is: Rs "<<total_bill<<endl;
122
}
int main()
{
string name_of_item;
int quantity,choice;
items *head= new items;
head->next=NULL;
head->item_name="tissues";
head->price=200;
head->Quantity=5;
head->next=new items;
head->next->item_name="lays";
head->next->price=20;
head->next->Quantity=50;
head->next->next=new items;
head->next->next->item_name="cornetto";
head->next->next->price=45;
head->next->next->Quantity=75;
head->next->next->next=new items;
head->next->next->next->item_name="milk";
head->next->next->next->price=100;
head->next->next->next->Quantity=44;
head->next->next->next->next=NULL;
while(true){
cout<<"=============:STORE MENU:============="<<endl;
cout<<"Press 1 to See list of available items"<<endl;
cout<<"Press 2 to Add an item to your cart"<<endl;
cout<<"Press 3 to Delete an item from your cart"<<endl;
cout<<"Press 4 to View your cart"<<endl;
cin>>choice;
switch(choice)
{
case 1:
Display_items(head);
break;
case 2:
cout<<"Enter name of item: ";
cin>>name_of_item;
cout<<"Enter quantity: ";
cin>>quantity;
Add_to_cart(head,name_of_item,quantity);
break;
case 3:
cout<<"Enter name of item: ";
cin>>name_of_item;
cout<<"Enter quantity: ";
cin>>quantity;
Delete_from_cart(head,name_of_item);
break;
case 4:
Display_cart();
123
break;
default:
cout<<"Invalid choice!";
break;
}
}
}
124