Chapter 1-3
Chapter 1-3
INTRODUCTION TO DATA
STRUCTURES AND
ALGORITHMS
INTRODUCTION
A program is written code in order to solve a
problem.
A solution to a problem actually consists of two
things:
A way to organize the data
Sequence of steps to solve the problem
In a stack, operations can be performing only from one end (top here).
iii. Queue Data Structure
Unlike stack, the queue data structure works in the FIFO principle where first
element stored in the queue will be removed first.
It works just like a queue of people in the ticket counter where first person on the
queue will get the ticket first.
In a queue, addition and removal are performed from separate ends.
Linked list
Nonlinear data structures
Unlike linear data structures, elements in non-linear data structures are
not in any sequence.
Instead they are arranged in a hierarchical manner where one element
will be connected to one or more elements.
Non-linear data structures are further divided into graph and tree based
data structures.
1. Trees Data Structure
a tree is a collection of vertices and edges. However, in tree data
structure, there can only be one edge between two vertices.
It contains a central node, structural nodes, and sub-
nodes connected via edges.
also consists of roots, branches, and leaves connected.
It
Popular Tree based Data Structure
Binary Tree
Binary Search Tree
AVL Tree
B-Tree Tree data Tree structure example
B+ Tree
Red-Black Tree
2. GRAPH DATA STRUCTURE
It utilized to address problems of the real world in
which it denotes the problem area as a network such
as social networks, circuit networks, and telephone
networks
In graph data structure, each node is called vertex and each
vertex is connected to other vertices through edges.
Trivial Graph
Directed Graph
Complete Graph
9
Linear Vs Non-linear Data Structures
Now that we know about linear and non-linear data structures, let's see the major
differences between them.
Linear Data Structures Non Linear Data Structures
The data items are arranged in sequential The data items are arranged in non-
order, one after the other. sequential order (hierarchical manner).
All the items are present on the single layer. The data items are present at different layers.
It can be traversed on a single run. That is, if It requires multiple runs. That is, if we start
we start from the first element, we can from the first element it might not be
traverse all the elements sequentially in a possible to traverse all the elements in a
single pass. single pass.
18
o There is no generally accepted set of rules for algorithm
analysis. However, an exact count of operations is
commonly used.
Analysis Rules:
1. We assume an arbitrary time unit.
2. Execution of one of the following operations takes time 1:
Assignment Operation
Single Input/Output Operation
Single Boolean Operations
Single Arithmetic Operations
Function Return
3. Running time of a selection statement (if, switch) is the
time for the condition evaluation + the maximum of the
running times for the individual clauses in the
selection. 19
Example:
int x;
int sum=0;
if(a>b)
{
sum= a+b;
cout<<sum;
}
else
{
cout<<b;
}
T(n) = 1 +1+max(3,1) = 5
20
4. Loops: Running time for a loop is equal to the
running time for the statements inside the loop *
number of iterations.
o The total running time of a statement inside a group of
nested loops is the running time of the statements
multiplied by the product of the sizes of all the loops.
For nested loops, analyze inside out.
Always assume that the loop executes the
maximum number of iterations possible.
5. Running time of a function call is 1 for
setup + the time for any parameter
calculations + the time required for the
execution of the function body.
21
Examples:
1. int count(){
int k=0;
cout<< “Enter an integer”;
cin>>n;
for (i=0;i<n;i++)
k=k+1;
return 0;
}
Time Units to Compute
-------------------------------------------------
1 for the assignment statement: int k=0
1 for the output statement.
1 for the input statement.
In the for loop:
1 assignment, n+1 tests, and n increments.
n loops of 2 units for an assignment, and an addition. 22
27
Conditionals: Formally
If (test) s1 else s2: Compute the maximum of the running
time for s1 and s2.
28
ASYMPTOTIC NOTATIONS
The commonly used asymptotic notations used for
calculating the running time complexity of an algorithm
is given below:
Big oh Notation (O)
It measures the worst case of time complexity.
Omega Notation (Ω)
It basically describes the best-case scenario
It determines the fastest time of an algorithm.
case is same.
29
Little O (o())
It describes upper bound isn't asymptotically tight.
Big O, Little O, Omega & Theta
Big O: “f(n) is O(g(n))” iff for some constants c and N₀, f(N) ≤
cg(N) for all N > N₀
Omega: “f(n) is Ω(g(n))” iff for some constants c and N₀, f(N) ≥
cg(N) for all N > N₀
Theta: “f(n) is Θ(g(n))” iff f(n) is O(g(n)) and f(n) is Ω(g(n))
Little O: “f(n) is o(g(n))” iff f(n) is O(g(n)) and f(n) is not Θ(g(n))
Formal Definition of Big O, Omega, Theta and Little O
In plain words:
Big O (O()) describes the upper bound of the complexity.
Omega (Ω()) describes the lower bound of the complexity.
else return -1 index. (-1 shows that the key is not in the list).
The computational time for this algorithm is proportional to
38
48
49
50
2.2.3. Bubble Sort
Bubble sort is the simplest algorithm to implement and the slowest
algorithm on very large inputs.
Basic Idea: Loop through array from i=0 to n and swap adjacent
elements if they are out of order.
51
52
53
54
CHAPTER THREE
LINKED LISTS
55
3.1. Definition
A linked list is a linear data structure, in which the elements
are not stored at contiguous memory locations.
The elements in a linked list are linked using pointers as
shown in the below image:
59
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.
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.
Representation:
A linked list is represented by a pointer to the first node of the
linked list.
The first node is called the head. If the linked list is empty,
then the value of the head is NULL.
There are four types of linked lists: singly-linked list, doubly-
60
linked list, circular linked list and doubly circular linked list.
The singly-linked list contains nodes that only point to
the next node.
The C++ doubly linked list has nodes that can point
towards both the next and the previous node.
61
3.3. Singly Linked Lists
A node has two parts:
the data part and
the next part.
The data part contains the stored data, and
the next part provides the address of the next node.
The first node of a linked list is called the head, and the
last node is called the tail.
The list starts traversing from the head, while the tail
ends the list by pointing at NULL.
62
1. Creating Linked Lists in C++
A linked list is a data structure that is built from
structures and pointers.
It forms a chain of "nodes" with pointers representing
the links of the chain and holding the entire thing
together.
A linked list can be represented by a diagram like this
one:
This linked list has four nodes in it, each with a link to the next node in the series. The last
node has a link to the special value NULL, which any pointer (whatever its type) can point to,
to show that it is the last link in the chain. There is also another special pointer, called Start63
(also called head), which points to the first link in the chain so that we can keep track of it.
Linked list example add_node(2);
#include<iostream.h> display();
int add_node(int n); add_node(7);
int display(); display();
struct node }
{ int add_node(int n) {
int data; node *temp=new node;
node *next; node *temp2;
}; temp->data=n;
node *head=NULL; temp->next=NULL;
int main( ) if(head==NULL)
{ {
display(); head=temp;
add_node(1); head->next=NULL; 64
display(); }
else cout<<temp->data<<endl;
{temp2=head; temp=temp->next;
while(temp2->next!=NULL) }
temp2=temp2->next; }
temp2->next=temp;
}
}
int display()
{
node *temp;
temp=head;
while(temp!=NULL)
{
65
ADDING A NODE TO THE FRONT
int insert_front(int x)
{
node *temp=new node;
temp->data=x;
temp->next=NULL;
if(head==NULL)
head=temp;
else{
temp->next=head;
head=temp;
}
66
}
DELETING FROM THE FRONT
int delet_front()
{
node *temp;
if(head==NULL)
cout<<“No data inside\n”;
else{
temp=head;
head=head->next;
delete temp;
} 67
}
DELETING ANY SPECIFIC NODE
int delet_any(int x)
{ node *temp, *temp3;
if(head==NULL)
cout<<“No data inside\n”;
elseif(head->data==x)
{ temp=head;
head=head->next;
delete temp; }
else {
temp=head;
while(temp->data!=x)
{ temp3=temp;
temp=temp->next; }
temp3->next=temp->next;
delete temp;
68
}
}
DELETING A NODE
int delet_end()
{ node *temp, *temp3;
if(head==NULL)
cout<<“No data inside\n”;
else{
temp=head;
while(temp->next!=NULL)
{ temp3=temp;
temp=temp->next;
}
temp3->next=NULL;
delete temp; 69
}
}
3.4. Doubly Linked Lists
A doubly linked list is one where there are links from each
node in both directions:
Each node in the list has two pointers, one to the next node
and one to the previous one - again, the ends of the list are
defined by NULL pointers.
There is no pointer to the start of the list. Instead, there is
simply a pointer to some position in the list that can be
moved left or right.
70
The reason we needed a start pointer in the ordinary linked list is
because, having moved on from one node to another, we can't
easily move back, so without the start pointer, we would lose
track of all the nodes in the list that we have already passed.
With the doubly linked list, we can move the current pointer
backwards and forwards at will.
3.4.1. Creating Doubly Linked Lists
The nodes for a doubly linked list would be defined as follows:
struct node
{ char name[20];
node *nxt; // Pointer to next node
node *prv; // Pointer to previous node
};
node *current;
current = new node;
current->name = "Fred"; 71
current->nxt = NULL;
current->prv = NULL;
We have also included some code to declare the first
node and set its pointers to NULL. It gives the following
situation:
72
3.3.2. Adding a Node to a Doubly Linked List
void add_node_at_start (string new_name)
{ // Declare a temporary pointer and move it to the start
node *temp = current;
while (temp->prv != NULL)
temp = temp->prv; // Declare a new node and link it in
node *temp2;
temp2 = new node;
temp2->name = new_name; // Store the new name in the node
temp2->prv = NULL; // This is the new start of the list
temp2->nxt = temp; // Links to current list
temp->prv = temp2;
current=temp2;
} 73
void add_node_at_end ()
{ // Declare a temporary pointer and move it to the end
node *temp = current;
while (temp->nxt != NULL)
temp = temp->nxt; // Declare a new node and link it in
node *temp2;
temp2 = new node;
temp2->name = new_name; // Store the new name in the node
temp2->nxt = NULL; // This is the new start of the list
temp2->prv = temp; // Links to current list
temp->nxt = temp2;
}
Here, the new name is passed to the appropriate
function as a parameter. 74
We'll go through the function for adding a node to the
right-most end of the list.
The method is similar for adding a node at the other end.
Firstly, a temporary pointer is set up and is made to march
along the list until it points to last node in the list.
Start_Ptr
After that, a new node is declared, and the name is copied into
it. The nxt pointer of this new node is set to NULL to indicate
that this node will be the new end of the list.
The prv pointer of the new node is linked into the last node of
the existing list.
The nxt pointer of the current end of the list is set to the new
node.
75
Deleting a node from the end of a doubly
linked list
void delete_end()
{
node *temp;
if(tail==NULL)
cout <<"No data inside\n";
else
{
temp = tail;
tail = tail->prev;
tail->next = NULL;
delete temp;
76
}
}
Deleting a node from the front of a doubly
linked list
void delete_front()
{
node *temp;
if(head==NULL)
cout <<"No data inside\n";
else
{
temp = head;
head = head->next;
head->prev = NULL;
delete temp; 77
}
}
Examples of Doubly Linked List
#include<iostream.h> if(current==NULL)
struct Dlink {
{ current = temp;
int data;
Dlink *next; t=current;
Dlink *prev; }
}; else
Dlink *current = NULL,*t; {
void creatDlink(int d) t->next = temp;
{
Dlink *temp; temp->prev = t;
temp = new Dlink; t=temp;
temp-> data = d; }
temp -> next= NULL; }
temp->prev=NULL;
void DDeleteFront() if(current == NULL)
{ cout<<"Empty list.\n";
Dlink *temp; else
if(current == NULL) {
cout<<"Empty list.\n";
temp1 = current;
else
{ while(temp1->next !=
NULL)
temp = current;
current = current->next; {
current->prev = NULL; temp2 = temp1;
delete temp; temp1 = temp1->next;
}} }
void DDeleteEnd() temp2->next = NULL;
{ delete temp1;
Dlink *temp1,*temp2; }}
void DinsertEnd(int item)
{
void Ddisplay()
Dlink *temp1,*temp2; {
temp1=current;
Dlink *t;
temp2=new Dlink;
temp2->data=item; if(current == NULL)
temp2->next=NULL; cout<<"Empty list.\n";
while(temp1->next!=NULL)
{temp1=temp1->next;} else
temp1->next=temp2; {
temp2->prev=temp1;
}
t = current;
void DinsertFront(int item) while(t != NULL)
{
Dlink *temp;
{
temp=new Dlink; cout<<t-> data<<" ";
temp->data=item;
t = t->next;
temp->prev=NULL;
temp->next=current; }}
current->prev=temp; cout<<endl;
current=temp;
} }
int main()
cout<<"\nEnter an element to INSERT at
{ the END: ";
int m, value; cin>>value;
cout<<"How many elements in the cout<<"\nAfter inserting "<<value<<" at
doubly linked list? "; the end, it becomes: \n\t";
cin>>m; DinsertEnd(value);
for(int i=1;i<=m ; i ++) Ddisplay();
{ cout<<"\nEnter an element to INSERT at
cout<<"\tEnter element "<<i<<" :"; FRONT: ";
cin>>value; cin>>value;
creatDlink(value); cout<<"\nAfter inserting "<<value<<" at
} front, it becomes: \n\t";
cout<<"\nThe doubly linked list DinsertFront(value);
elements are: \n\t"; Ddisplay();
Ddisplay(); cout<<"\nAfter the END element is
cout<<"\nWhen FRONT element is DELETED, it becomes: \n\t";
DELETED, it becomes:\n\t"; DDeleteEnd();
DDeleteFront(); Ddisplay();
Ddisplay(); }