CS301 Mid-Term Notes 1-22
CS301 Mid-Term Notes 1-22
Data structures
Data structures are used to store data in a computer in an organized form. In C++
language, Different types of data structures are; Array, Stack Queue, Linked List,
and Tree. Sorting technique is merge sort, shell sort, bubble sort, Quick sort,
Selection sort, Heap sort etc.
Organizing Data
The choice of data structure and algorithm can make the difference between
a program running in a few seconds or many days.
Data Structures
1. Arrays
2. Linked Lists
3. Stacks
4. Queues
5. Hash Tables
6. Trees
7. Heaps
8. Graphs
Arrays
An ordered and index able sequence of values. C++ supports arrays of a single
dimension (a vector) or of multiple dimensions. Elementary data structure that
exists as built-in in most programming languages.
#include <iostream>
int x[6];
int j;
x[j] = 2*j;
Array operations
Inserting elements to an array and deleting elements from an array cannot be done
straight away as arrays are fixed in size. If you want to insert an element to an
array, first you will have to create a new array with increased size (current size +
1), copy the existing elements and add the new element. The same goes for the
deletion with a new array of reduced size.
Applications of arrays
Used as the building blocks to build other data structures such as array lists,
heaps, hash tables, vectors and matrices.
Used for different sorting algorithms such as insertion sort, quick sort,
bubble sort and merge sort.
Real life:
a. shopping list,
b. groceries list,
Lists LONG OR
SHORT OR Quiz
A list is collection of items that are all of the same type (grocery items,
integers, names).
The items, or elements of the list, are stored in some particular order.
It is possible to insert new elements into various positions in the list and
remove any element of the list.
In this list, a3, is the first element, a1 is the second element, and so on
The order is important here; this is not just a random collection of elements,
it is an ordered collection.
What is meant by “particular position” we have used “?” for List Operations?
There are two possibilities:
1. Use the actual index of element: insert after element 3, get element
number 6. This approach is taken by arrays.
Linked Lists
A linked list is a sequential structure that consists of a sequence of items in linear
order which is linked to each other. Hence, you have to access data sequentially
and random access is not possible. Linked lists provide a simple and flexible
representation of dynamic sets. Let’s consider the following terms regarding linked
lists.
• Each node contains a key and a pointer to its successor node, known as next.
• The attribute named head points to the first element of the linked list.
• Singly linked list — Traversal of items can be done in the forward direction only.
• Doubly linked list — Traversal of items can be done in both forward and
backward directions. Nodes consist of an additional pointer known as prev,
pointing to the previous node.
• Circular linked lists — Linked lists where the prev pointer of the head points to
the tail and the next pointer of the tail points to the head
• Singly linked list — Traversal of items can be done in the forward direction only.
• Doubly linked list — Traversal of items can be done in both forward and
backward directions. Nodes consist of an additional pointer known as prev,
pointing to the previous node.
• Circular linked lists — Linked lists where the prev pointer of the head points to
the tail and the next pointer of the tail points to the head. Linked list operations
• Search: Find the first element with the key k in the given linked list by a simple
linear search and returns a pointer to this element.
• Insert: Insert a key to the linked list. An insertion can be done in 3 different ways;
insert at the beginning of the list, insert at the end of the list and insert in the
middle of the list.
• Delete: Removes an element x from a given linked list. You cannot delete a node
by a single step.
A deletion can be done in 3 different ways; delete from the beginning of the list,
delete from the end of the list and delete from the middle of the list.
I. add Method
II. next Method
III. remove Method
IV. find Method
V. Other Methods
List Implementation
In C language, a linked list can be implemented using structure and pointers. Strict
Linked List {int data; struct Linked List *next; }; The above definition is used to
create every node in the list. The data field stores the element and the next is a
pointer to store the address of the next node.
Implementing Lists using an array: for example, the list of integers (2, 6, 8,
7, 1) could be represented as:
Current Size
5 5 A 6 8 7
3
1 1 2 3 4 5
I. List Implementation by add Method
Now we will talk about adding an element to the list. Suppose there is a call
to add an element in the list Add (9); current position is 3. The new list
would thus be: (2, 6, 8, 9, 7, and 1)
We will need to shift everything to the right of 8 one place to the right to
make place for the new element ‘9’.
The removal of the element will be carried out as follows. Suppose there are
6 elements (2, 6, 8, 9, 7, and 1) in the list. The current pointer is pointing to
the position 5 that has the value 7.We fill the blank spot left by the removal
of 7 by shifting the values to the right of position 5 over to the left one
space.
int find(int X)
{
int j;
for(j=1; j < size+1; j++ )
if( A[j] == X ) break;
Now we analyze the implementation of the list while using an array internally. We
analyze different methods used for the implementation of the list.
add
We have to move every element to the right of current to make space for the
new element. Worst-case is when we insert at the beginning; we have to
move every element right one place.
Average-case: on average we may have to move half of the elements.
Remove
Find
Linked List
Object Next
The object field will hold the actual list element. The next field in the structure will
hold the starting location of the next node. Chain the nodes together to form a
linked list.
Next 2 6
Need a head to point to the first node of the list. Otherwise we won’t know
where the start of the list is.
The next field in the last node points to nothing. We will place the memory
address NULL which is guaranteed to be inaccessible.
Linked List
The linked list data structure provides operations to work on the nodes inside the
list. The first operation we are going to discuss here is to create a new node in the
memory. Add (9): Create a new node in memory to hold ‘9’
Node* new Node = new Node (9);
9
Link the new node into the list
class Node {
public:
{ this->nextNode = nextNode; };
private:
int object;
Node *nextNode;
};
#include <stdlib.h>
#include "Node.cpp"
class List {
public:
// Constructor
List() {
headNode->setNext(NULL);
currentNode = NULL;
size = 0;
};
newNode->set(addObject);
newNode->setNext(currentNode->getNext());
currentNode->setNext( newNode );
lastCurrentNode = currentNode;
currentNode = newNode;
else {
newNode->setNext(NULL);
headNode->setNext(newNode);
lastCurrentNode = headNode;
currentNode = newNode;
size++;
};
int get() {
if (currentNode != NULL)
return currentNode->get();
};
bool next() {
lastCurrentNode = currentNode;
currentNode = currentNode->getNext();
return false;
else
return true;
};
In the previous lecture, we discussed the methods of linked list. These methods
form the interface of the link list.
// list element
void start() {
lastCurrentNode = headNode;
currentNode = headNode;
};
There are two statements in this method. We assign the value of head Node to both
last Current Node and current Node. These two pointers point at different nodes of
the list..
void remove() {
currentNode != headNode) {
lastCurrentNode->setNext(currentNode->getNext());
delete currentNode;
currentNode = lastCurrentNode->getNext();
size--;
};
We will now see how a node can be removed from the link list. We use the method
remove for this purpose.
C++ Code for Linked List
int length()
return size;
};
private:
int size;
Node *headNode;
#include <iostream>
#include <stdlib.h>
#include "List.cpp"
List list;
list.start();
while (list.next())
We will see whether it is useful or not. We will see its cost and benefit with respect
to time and memory.
add
We simply insert the new node after the current node. So add is a one-
step operation.
remove
find
back
Moving the current pointer back one node requires traversing the list
from the start until the node whose next pointer points to current node.
Doubly-linked List
Moving forward in a singly-linked list is easy; moving backwards is not so easy.
To move back one node, we have to start at the head of the singly-linked list and
move forward until the node before the current. To avoid this we can use two
pointers in a node: one to point to next node and another to point to the previous
node:
class Node {
public:
{ this->nextNode = nextNode; };
{ this->prevNode = prevNode; };
private:
int object;
Node* nextNode;
Node* prevNode;
};
Circularly-linked lists
The next field in the last node in a singly-linked list is set to NULL. Moving along
a singly-linked list has to be done in a watchful manner. Doubly-linked lists have
two NULL pointers: prev in the first node and next in the last node. A way around
this potential hazard is to link the last node with the first node in the list to create a
circularly-linked list.
A case where circularly linked list comes in handy is the solution of the Josephus
Problem. Consider there are 10 persons. They would like to choose a leader. The
way they decide is that all 10 sit in a circle. They start a count with person 1 and go
in clockwise direction and skip 3. Person 4 reached is eliminated. The count starts
with the fifth and the next person to go is the fourth in count. Eventually, a single
person remains.
Example
#include "CList.cpp"
CList list;
list.start();
list.remove();
Josephus Problem
#include "CList.cpp"
CList list;
list.start();
list.remove();
}
Using a circularly-linked list made the solution trivial. The solution would have
been more difficult if an array had been used. This illustrates the fact that the
choice of the appropriate data structures can significantly simplify an algorithm. It
can make the algorithm much faster and efficient. Later we will see how some
elegant data structures lie at the heart of major algorithms. An entire CS course
“Design and Analysis of Algorithms” is devoted to this topic.
Some problems are circular and a circular data structure would be more natural
when used to represent it. The entire list can be traversed starting from any node
(traverse means visit every node just once) fewer special cases when coding (all
Abstract Data type (ADT) is a type (or class) for objects whose behavior is
defined by a set of value and a set of operations It is called “abstract” because it
gives an implementation-independent view. The process of providing only the
essentials and hiding the details is known as abstraction.
Stacks
• Pop() – remove the top element of the stack and return it.
• Top() – return the top element without removing it from the stack.
The last element to go into the stack is the first to come out: LIFO – Last in First
Out. Have Is Empty () Boolean function that returns true if stack is empty, false
otherwise. Throw Stack Empty exception: advanced C++ concept.
Worst case for insertion and deletion from an array when insert and delete from the
beginning: shift elements to the left. Best case for insert and delete is at the end of
the array – no need to shift any elements. Implement push () and pop () by
inserting and deleting at the end of an array.
In case of an array, it is possible that the array may “fill-up” if we push enough
elements. Have a Boolean function Is Full () which returns true is stack (array) is
full, false otherwise. We would call this function before calling push(x).
int pop()
return A[current--];
void push(int x)
A[++current] = x;
int top()
return A[current];
int IsEmpty()
return ( current == -1 );
int IsFull()
A quick examination shows that all five operations take constant time.
We can avoid the size limitation of a stack implemented with an array by using a
linked list to hold the stack elements. As with array, however, we need to decide
where to insert elements in the list and where to delete them so that push and pop
will run the fastest.
For a singly-linked list, insert at start or end takes constant time using the head and
current pointers respectively. Removing an element at the start is constant time but
removal at the end required traversing the list to the node one before the last. Make
sense to place stack elements at the start of the list because insert and removal are
constant time.
int pop()
int x = head->get();
Node* p = head;
head = head->getNext();
delete p;
return x;
void push(int x)
newNode->set(x);
newNode->setNext(head);
head = newNode;
int top()
return head->get();
int IsEmpty()
{
Allocating and deal locating memory for list nodes does take more time than
reallocated array. List uses only as much memory as required by the nodes; array
requires allocation ahead of time. List pointers (head, next) require extra memory.
Array has an upper limit; List is limited by dynamic memory allocation.
Use of Stack
Example of use: prefix, infix, postfix expressions. Consider the expression A+B:
we think of applying the operator “+” to the operands A and B. “+” are termed a
binary operator: it takes two operands. Writing the sum as A+B is called the infix
form of the expression.
The prefixes “pre” and “post” refer to the position of the operator with respect to
the two operands.
I. Conversion to postfix
A+(B*C) infix form
(A + B ) * C infix form
Precedence of Operators
Exponentiation
Multiplication/division *, /
Addition/subtraction +, -
A B C means A (B C)
Infix to Postfix
Infix Postfix
A+B AB+
A B * C – D + E/F A B C*D –
E F/+
Infix Postfix
A+B AB+
12 + 60 – 23 12 60 + 23 –
(A + B)*(C – D ) AB+CD–*
A B * C – D + E/F A B C*D –
E F/+
Each operator in a postfix expression refers to the previous two operands. Each
time we read an operand, we push it on a stack. When we reach an operator, we
pop the two operands from the top of the stack, apply the operator and push the
result back on the stack.
if( e is an operand )
s.push( e );
else {
op2 = s.pop();
op1 = s.pop();
s.push( value );
finalresult = s.pop();
Evaluate 6 2 3 + - 3 8 2 / + * 2 3 +
6 6
2 6,2
3 6,2,3
+ 2 3 5 6,5
- 6 5 1 1
3 6 5 1 1,3
8 6 5 1 1,3,8
2 6 5 1 1,3,8,2
/ 8 2 4 1,3,4
+ 3 4 7 1,7
* 1 7 7 7
2 1 7 7 7,2
7 2 49 49
3 7 2 49 49,3
+ 49 3 52 52
Consider the infix expressions ‘A+B*C’ and ‘(A+B)*C’. The postfix versions are
‘ABC*+’ and ‘AB+C*’. The order of operands in postfix is the same as the infix.
In scanning from left to right, the operand ‘A’ can be inserted into postfix
expression. The ‘+’ cannot be inserted until its second operand has been scanned
and inserted. The ‘+’ has to be stored away until its proper position is found. When
‘B’ is seen, it is immediately inserted into the postfix expression. Can the ‘+’ be
inserted now? In the case of ‘A+B*C’ cannot because * has precedence. In case
of ‘(A+B)*C’, the closing parenthesis indicates that ‘+’ must be performed first.
Assume the existence of a function ‘prcd(op1,op2)’ where op1 and op2 are two
operators. Prcd(op1,op2) returns TRUE if op1 has precedence over op2, FASLE
otherwise.
prcd(‘*’,’+’) is TRUE
prcd(‘+’,’+’) is TRUE
prcd(‘+’,’*’) is FALSE
Here is the algorithm that converts infix expression to its postfix form.The infix
expression is without parenthesis.
1. Stack s;
2. While( not end of input ) {
4. if( c is an operand )
6. else {
8. op = s.pop();
10. }
11. s.push( c );
12. }
14. op = s.pop();
16. }
Example: A + B * C
A A
+ A +
B AB +
* AB +*
C ABC + *
ABC * +
ABC * +
When an open parenthesis ‘(‘ is read, it must be pushed on the stack. This can be
done by setting prcd(op,‘(‘ ) to be FALSE. Also, prcd( ‘(‘,op ) == FALSE which
ensures that an operator after ‘(‘ is pushed on the stack. When a ‘)’ is read, all
operators up to the first ‘(‘ must be popped and placed in the postfix string. To do
this, prcd( op,’)’ ) == TRUE. Both the ‘(‘ and the ‘)’ must be discarded: prcd(
‘(‘,’)’ ) == FALSE.
else
s.pop(); // discard the ‘(‘
prcd( op, ‘(’ ) = FALSE for any operator other than ‘(’
prcd( op, ‘)’ ) = TRUE for any operator other than ‘(‘
Example: (A + B) * C
( (
A A (
+ A (+
B AB (+
) AB +
* AB + *
C AB + C *
AB + C *
C++ Templates
We can use C++ Templates to create a “template” of a stack class. Instantiate float
stack, char stack, or stack for any type of element we want.
Stack.h:
class Stack {
public:
Stack();
T pop(void);
T peek(void);
~Stack();
private:
int top;
T* nodes;
};
Implementation
Now we will see how to implement this stack class in our program. We save this
code in the file named Stack.cpp.
Stack.cpp
#include <iostream.h>
#include <stdlib.h>
#include "Stack.cpp"
#define MAXSTACKSIZE 50
Stack<T>::Stack()
top = -1;
Stack<T>::~Stack()
delete nodes;
}
int Stack<T>::empty(void)
return 0;
T Stack<T>::pop(void)
T x;
if( !empty() ) {
x = nodes[top--];
return x;
return x;
main.cpp
#include "Stack.cpp"
Stack<int> intstack;
Stack<char> charstack;
int x=10, y=20;
intstack.push(x); intstack.push(y);
charstack.push(c); charstack.push(d);
last
argument
second
n*4(% argument
first
esp)
……… argument
return
. address
Example: consider the function:
return (a + b) / 2;
# 8(%esp) b
# 4(%esp) a
return (a + b) / 2;
.globl _i_avg
_i_avg:
Memory Organization
When a program (.exe) is run, it is loaded in memory. It becomes a process. The
process is given a block of memory. [Control-Alt-DEL]
Data Structures Lecture No. 09
Memory Organization
The memory is organized in the form of a cell; each cell is able to be identified
with a unique number called address. Each cell is able to recognize control signals
such as “read” and “write”, generated by CPU when it wants to read or write
address. To access the instruction CPU generates the memory request.
A code section that contains the binary version of the actual code of the
program written in some language like C/C++
A section for static data including global variables
A stack and – Finally
a heap
Here is stack layout when function F calls function G:
Queues
A queue is a FIFO (First In First Out — the element placed at first can be accessed
at first) structure which can be commonly found in many programming languages.
This structure is named as “queue” because it resembles a real-world queue —
people waiting in a queue. Queue operations given below are the 2 basic operations
that can be performed on a queue.
Implementing Queue
Seems best that head of the linked list be the front of the queue so that all
removes will be from the front.
int dequeue()
int x = front->get();
Node* p = front;
front = front->getNext();
delete p;
return x;
}
void enqueue(int x)
newNode->set(x);
newNode->setNext(NULL);
rear->setNext(newNode);
rear = newNode;
int front()
return front->get();
int isEmpty()
If we use an array to hold queue elements, both insertions and removal at the front
(start) of the array are expensive. This is because we may have to shift up to “n”
elements. For the stack, we needed only one end; for queue we need both. To get
around this, we will not shift upon removal of an element.
int dequeue()
int x = front->get();
Node* p = front;
front = front->getNext();
delete p;
return x;
void enqueue(int x)
newNode->set(x);
newNode->setNext(NULL);
rear->setNext(newNode);
rear = newNode;
int front()
{
return front->get();
int isEmpty()
If we use an array to hold queue elements, both insertions and removal at the front
(start) of the array are expensive. This is because we may have to shift up to “n”
elements. For the stack, we needed only one end; for queue we need both. To get
around this, we will not shift upon removal of an element.
Queues
A customer enters the bank at a specific time (t1) desiring to conduct a transaction.
Any one of the four tellers can attend to the customer. The transaction (withdraws,
deposit) will take a certain period of time (t 2). If a teller is free, the teller can
process the customer’s transaction immediately and the customer leaves the bank
at t1+t2. It is possible that none of the four tellers is free in which case there is a line
of customers are each teller. An arriving customer proceeds to the back of the
shortest line and waits for his turn. The customer leaves the bank at t 2 time units
after reaching the front of the line. The time spent at the bank is t 2 plus time
waiting in line.
Simulation Models
clock = 0;
if customer.arrivaltime == clock
clock = clock + 1;
Don’t wait for the clock to tic until the next event. Compute the time of next event
and maintain a list of events in increasing order of time. Remove an event from the
list in a loop and process it.
Maintain a queue of events. Remove the event with the earliest time from the
queue and process it. As new events are created, insert them in the queue. A queue
where the DE queue operation depends not on FIFO is called a priority queue.
Development of the C++ code to carry out the simulation. We will need the queue
data structure. We will need the priority queue. Information about arriving
customers will be placed in an input file. Each line of the file contains the items
(arrival time, transaction duration).
00 30 10 <- customer 1
00 35 05 <- customer 2
00 40 08
00 45 02
00 50 05
00 55 12
01 00 13
01 01 09
“00 30 10” means Customer 1 arrives 30 minutes after bank opens and will need
10 minutes for his transaction. “01 01 09” means customer arrives one hour and
one minute after bank opens and transaction will take 9 minutes.
Simulation Procedure
The first event to occur is the arrival of the first customer. This event placed in the
priority queue. Initially, the four teller queues are empty.
If that customer is the only one on a teller queue, a event for his departure is
placed on the priority queue.
At the same time, the next input line is read and an arrival event is placed in
the priority queue.
When a departure event is removed from the event priority queue, the
customer node is removed from the teller queue.
The total time spent by the customer is computed: it is the time spent in the
queue waiting and the time taken for the transaction.
At the end of the simulation, this total time divided by the total customers
served will be average time spent by customers.
#include <iostream>
#include <string>
#include <strstream.h>
#include "Customer.cpp"
#include "Queue.h"
#include "PriorityQueue.cpp"
#include "Event.cpp"
int totalTime;
int count = 0;
int customerNo = 0;
Customer* c;
Event* nextEvent;
readNewCustomer(data);
nextEvent = pq.remove();
c = nextEvent->getCustomer();
processArrival(data, customerNo,
Let’s discuss the function readNewCustomer (). This function is used to read the
data from the file.
int hour,min,duration;
customerNo++;
hour*60+min, duration);
else {
Let’s see the function processArrival(). We have decided that when the customer
arrives and no teller is available, he will go to the shortest queue.
Event* event)
int i, small, j = 0;
small = q[0].length();
small = q[i].length(); j = i;
duration );
if( q[j].length() == 1 ) {
c->setDepartureTime( arrTime+duration);
pq.insert(e);
readNewCustomer(data);
There may be a case that before leaving the bank, more persons arrive and they
have to wait in the queue for their turn. We handle this scenario in the departure
routine. The code is:
Event* event)
count = count + 1;
cinq = q[qindex].front();
pq.insert( e );
}}
In the priority queue, we put the elements in the queue to get them from the queue
with a priority of the elements. Following is the C++ code of the priority queue.
Priority Queue
#include "Event.cpp"
#define PQMAX 30
classPriorityQueue {
public:
PriorityQueue() {
};
~PriorityQueue() {};
int full(void)
};
Event* remove()
Event* e = nodes[0];
nodes[j] = nodes[j+1];
return e;
return (Event*)NULL;
cout<< "remove - queue is empty." <<endl;
};
int insert(Event* e)
if( !full() ) {
rear = rear+1;
nodes[rear] = e;
size = size + 1;
return 1;
return 0;
};
};
Tree
Tree is an important data structure .In this structures the elements are in a line. We
put and get elements in and from a stack in linear order. Queue is also a linear data
structure as a line is developed in it. Tree is one of the non-linear data structures.
The figure given below is showing a genealogy tree of a family.
In this genealogy tree, the node at the top of the tree is Muhammad Aslam Khan i.e.
the head of the family. There are three nodes under this one. These are Sohail
Aslam,
Javed Aslam and Yasmeen Aslam. Then there are nodes under these three nodes
i.e.
the sons of these three family members.
Binary Tree
“A binary tree is a finite set of elements that is either empty or is partitioned into
three
disjoint subsets.”The first subset contains a single element called the root of the
tree. The other two subsets are themselves binary trees called the left and right sub-
trees”. Each element of a binary tree is called a node of the tree.
There are nine nodes are A, B,C, D, E, F, G, H, and I. The node A is at the top of
the tree. There are two lines from the node A to left and right sides towards node B
and C.There are also two lines from node B to left and right, leading to the nodes D
and E. node, E there is only one line that is to the left of the node and leads to node
G. node C towards the node F on right side of the node C. Then there are two lines
from node F that leads to the nodes H and I on left and right side.
Now we analyze this tree according to the mathematical definition of the binary
tree. The node A is the root of the tree. And tree structure (i.e. the nodes B, D, E, G
and their relation) on the left side of the node A is a sub-tree, called the Left
subtree.
Similarly the nodes (C, F, H and I) on the right side of the node A comprise the
subtree
termed as Right subtree. Thus we made three parts of the tree. One part
containsonly one node i.e. A while the second part has all the nodes on left side of
A, called as Left subtree. Similarly the third part is the Right subtree, containing
the nodes on right side of A.
Similarly if we put other links between different nodes, then the structure
thus
developed will not be a tree. The following figure is also an example of a structure
that is not a tree as there are multiple links between the different nodes.
In the relationship of these nodes, A is the parent node with B and C as the left and
right descendants respectively. C is the right descendant of A. we look at the node
B where the node D is its left descendant and E is its right descendant. We can use
the words descendant and child interchangeably. Now look at the nodes D, G, H
and I. These nodes are said leaf nodes as there is no descendant of these nodes. We
are just introducing the terminology here. In the algorithms, we use the words root
or parent, child or descendant.
Strictly Binary Tree
Binary tree is said to be a strictly binary tree if every non-leaf node in a binary tree
has non-empty left and right subtrees. Now all the non-leaf nodes (A, B, C, E and
F) have their left and right children so according to the definition, it is a strictly
binary tree.
Level
Now look at the tree, the leaf nodes of the tree are at level 3 and are H, I, J, K, L,
M,
N and O. There is no such a leaf node that is at some level other than the depth
level d
i.e. 3. All the leaf nodes of this tree are at level 3. So this is a complete binary tree.
The property of the binary tree is that its node can have a maximum of two
subtrees, called as left and right subtrees. The nodes increase at each level, as the
tree grows downward.
24+1 - 1 = 25 – 1 = 32 – 1 = 31
Thus the total number of nodes in the complete binary tree of depth 4 is 31.
Level of a Complete Binary Tree
We can find the depth of a complete binary tree if we know the total number of
nodes. If we have a complete binary tree with n total nodes, then by the equation of
the total number of nodes.
Lecture No. 12
There are a number of operations that can be defined for a binary tree.
One way of finding duplicates is to compare each number with all those that
precede it.
If the list of numbers is large and is growing, this procedure involves a large
number of comparisons.
A linked list could handle the growth but the comparisons would still be
large.
The first number in the list is placed in a node that is designated as the root
of a binary tree.
Initially, both left and right subtrees of the root are empty.
We take the next number and compare it with the number placed in the root.
Otherwise, we create a new tree node and put the new number in it.
The new node is made the left child of the root node if the second number is
less than the one in the root.
The new node is made the right child if the number is greater than the one in the
root.
9, 14, 5
#include <stdlib.h>
class TreeNode {
public:
// constructors
TreeNode()
{
this->object = NULL;
};
this->object = object;
};
Object* getInfo()
return this->object;
};
this->object = object;
};
TreeNode* getLeft()
return left;
};
{
this->left = left;
};
TreeNode *getRight()
return right;
};
this->right = right;
};
int isLeaf( )
return 1;
return 0;
};
private:
Object* object;
TreeNode* left;
TreeNode* right;
The code of main program file containing the main() and insert() functions.
#include <iostream>
#include <stdlib.h>
#include "TreeNode.cpp"
9, 14,5, -1};
root->setInfo( &x[0] );
insert(root, &x[i] );
p = q = root;
p = q;
else
q = p->getRight();
delete node;
p->setLeft( node );
else
p->setRight( node );
} // end of insert
Trace of insert
To execute this algorithm manually to insert the remaining numbers in the tree.
This will make the understanding more clear. This tree algorithm is going to be
used rigorously in the future and complete understanding of it will make
the complex future implementations easier to comprehend.
Lecture No. 13
Cost of Search
Given that a binary tree is level d deep. How long does it take to find out
whether a number is already present?
With the binary tree in place, we can write a routine find(x) that returns true
if the number x is present in the tree, false otherwise.
How many comparison are needed to find out if x is present in the tree?
If the binary tree is built out of n numbers, how many comparisons are
needed to find out if a number x is in the tree?
Recall that the depth of the complete binary tree built using ‘n’ nodes will be
log2(n+1) – 1.
For example, for n=100,000, log2(100001) is less than 20; the tree would be
20 levels deepIf the tree is complete binary or nearly complete, searching
through 100,000 numbers will require a maximum of 20 comparisons.
Cost of Search
A binary tree with the property that items in the left subtree are smaller than
the root and items are larger or equal in the right subtree is called a binary
search tree (BST).
The tree we built for searching for duplicate numbers was a binary search
tree.
We want to print all the values stored in the nodes of the tree.
(14,4,15), (14,15,4)
(15,4,14), (15,14,4)
In case of the general binary tree
(L,N,R), (L,R,N)
(N,L,R), (N,R,L)
(R,L,N), (R,N,L)
Preorder: (N,L,R)
Inorder: (L,N,R)
Postorder: (L,R,N)
preorder(treeNode->getLeft());
preorder(treeNode->getRight());
}
}
inorder(treeNode->getLeft());
inorder(treeNode->getRight());
Recursive Call
The caller function places the arguments on the stack and passes control
to the called function.
Recursion: preorder
preorder(14)
14
..preorder(4)
....preorder(3)
......preorder(null)
......preorder(null)
....preorder(9)
......preorder(7)
........preorder(5)
5
..........preorder(null)
..........preorder(null)
........preorder(null)
......preorder(null
..preorder(15)
15
....preorder(null)
....preorder(18)
18
......preorder(16)
16
........preorder(null)
........preorder(17)
17
..........preorder(null)
..........preorder(null)
......preorder(20)
20
........preorder(null)
........preorder(null)
inorder(14)
..inorder(4)
....inorder(3)
......inorder(null)
......inorder(null)
....inorder(9)
......inorder(7)
........inorder(5)
..........inorder(null)
..........inorder(null)
........inorder(null)
......inorder(null)
14
..inorder(15)
....inorder(null)
15
....inorder(18)
......inorder(16)
........inorder(null)
16
........inorder(17)
..........inorder(null)
17
..........inorder(null)
18
......inorder(20)
........inorder(null)
20
........inorder(null)
Recursion: inorder
inorder(14)
..inorder(4)
....inorder(3)
......inorder(null)
3
......inorder(null)
....inorder(9)
......inorder(7)
........inorder(5)
..........inorder(null)
..........inorder(null)
........inorder(null)
......inorder(null)
14
..inorder(15)
....inorder(null)
15
....inorder(18)
......inorder(16)
........inorder(null)
16
........inorder(17)
..........inorder(null)
17
..........inorder(null)
18
......inorder(20)
........inorder(null)
20
........inorder(null)
The stack will be used to store the tree nodes in the appropriate order.
Here, for example, is the routine for inorder traversal that uses a stack.
p = root;
do
while( p != NULL )
stack.push( p );
p = p->getLeft();
if( !stack.empty() )
p = stack.pop();
p = p->getRight();
Traversal Trace
3 3
4 4
5 5
7 7
9 9
14 14
15 15
16 16
17 17
18 18
20 20
Level-order Traversal
There is yet another way of traversing a binary tree that is not related to
recursive traversal procedures discussed previously.
Level-order: 14 4 15 3 9 18 7 16 20 5 17
Lecture No. 15
Level-order: 14 4 15 3 9 18 7 16 20 5 17
Surprisingly, if we use a queue instead of a stack, we can visit the nodes in level-
order.
Queue<TreeNode<int>* > q;
if( treeNode == NULL ) return;
q.enqueue( treeNode);
while( !q.empty() )
treeNode = q.dequeue();
if(treeNode->getLeft() != NULL )
q.enqueue( treeNode->getLeft());
if(treeNode->getRight() != NULL )
q.enqueue( treeNode->getRight());
Output: 14 4 15 3 9 18 7 16 20 5 17.
This is surely not a requirement. Any type of data can be stored in a tree
node.
Here, for example, is the C++ code to build a tree with character strings.
void wordTree()
"abash","accuse","economy","adhere","advise","cease",
"debunk","feeder","genius","fetch","chain", NULL};
root->setInfo( word[0] );
insert(root, word[i] );
p = q = root;
while( strcmp(info, p->getInfo()) != 0 && q != NULL )
p = q;
q = p->getLeft();
else
q = p->getRight();
<< endl;
delete node;
p->setLeft( node );
else
p->setRight( node );
} Output
The examples of binary trees so far have been storing integer data in
Lecture No. 16
If the node has one child, the node can be deleted after its parent adjusts a
pointer to bypass the node and connect to inorder successor.
The inorder traversal order has to be maintained after the delete.
The complicated case is when the node to be deleted has both left and right
subtrees.
The strategy is to replace the data of this node with the smallest data of the
right subtree and recursively delete that node.
locate inorder successor
Inorder successor will be the left-most node in the right subtree of 2.
The inorder successor will not have a left child because if it did, that child
would be the left-most node.
copy data from inorder successor.
delete nodeToDelete;
}
return tree;
}
BinarySearchTree.h
#ifndef _BINARY_SEARCH_TREE_H_
#define _BINARY_SEARCH_TREE_H_
#include <iostream.h> // For NULL
// Binary node and forward declaration
template <class EType>
class BinarySearchTree;
Lecture No. 17
Reference Variables
The symbol “&” has a few different purposes depending on where it occurs in
code.When it appears in front of a variable name, it is the address operator,
i.e., it returns the address of the variable in memory.
int x;
int* ptr = &x;
The symbol “&’ can also appear after a type in a function signature:
// example 1
oldVal = oldVal – 1;
return oldVal;
// example 2
*oldVal = *oldVal – 2;
return *oldVal;
// example 3
oldVal = oldVal – 3;
return oldVal;
}
void caller()
int retVal;
Reference Variables
void caller()
int retVal;
The & after int means that oldVal is an integer reference variable.
// example 3
oldVal = oldVal – 3;
return oldVal;
The idea is: the integer object myInt is used exactly as it exists in the caller.
The function simply reaches it through a different name, oldVal.
The function intMinus3 cannot use the name myInt because it is in the
caller’s scope.
But both variable names refer to the same object (same memory cell).
Lecture No. 18
Reference Variables
One should be careful about transient objects that are stored by reference in
data structures.
Consider the following code that stores and retrieves objects in a queue.
{
Customer c1(“irfan”);
Customer c2(“sohail”;
q.enqueue( c1 );
q.enqueue( c2 );
serviceCustomer() method
q.enqueue( c2 );
The pointer variables c1 and c2 are on the call stack. They will go but their
contents (addresses) are queued.
The Customer objects are created in the heap. They will live until explicitly
deleted.
Memory Organization
if a process has some destructive code then it will not harm any other process,
only its own destruction is caused. By the way, lot of viruses exploit the stack
overflow to change the memory contents and cause further destruction to the
system.
let’s see the figure below where the objects are created dynamically.
Reference Variables
Customer* c = q.dequeue();
Must use the c-> syntax because we get a pointer from the queue.The object is
still alive because it was created in the heap.
The parameter must remain constant for the life of the function.
If you try to change the value, e.g., parameter appears on the left hand side
of an assignment, the compiler will generate and error.
This also means that if the parameter is passed to another function, that
function must not change it either.
Use of const with reference parameters is very common.
This is puzzling; why are we passing something by reference and then make
it constant, i.e., don’t change it?
Doesn’t passing by reference mean we want to change it?
The answer is that, yes, we don’t want the function to change the parameter,
but neither do we want to use up time and memory creating and storing an
entire copy of it.
So, we make the original object available to the called function by using
pass-by-reference.
We also mark it constant so that the function will not alter it, even by
mistake.
Lecture No. 19
Use 2: The const keyword appears at the end of class member’s function
signature.
BST for 3 4 5 7 9 14 15 16 17 18 20
Balanced BST
We should keep the tree balanced.
One idea would be to have the left and right subtrees have the same height.
Balanced BST Does not force the tree to be shallow:
We could insist that every node must have left and right subtrees of same
height.
But this requires that the tree be a complete binary tree
To do this, there must have (2d+1 – 1) data items, where d is the depth of the
tree.
This is too rigid a condition.
AVL Tree
AVL Tree
To insert a node in a BST, we compare its data with the root node. If the new
data item is less than the root node item in a particular order, this data item
will hold its place in the left subtree of the root. Now we compare the new
data item with the root of this left subtree and decide its place. Thus at last,
the new data item becomes a leaf node at a proper place. After inserting
the new data item, if we traverse the tree with the inorder traversal,
then that data item will become at its appropriate position in the data
items.
We will check the balance after each insert and rebalance if necessary using
rotations.
Inser t(1)
Insert (2)
Insert (3) single left rotation
Insert( 3)
So we can see that after the rotation, the tree has become balanced. The figure
reflects that the balance of node 1, 2 and 3 is 0. We see that the inorder traversal of
the above tree before rotation (tree on left hand side) is 1 2 3. Now if we
traverse the tree after rotation (tree on right hand side) by in order traversal, it is
also 1 2 3. With respect to the inorder traversal, both the traversals are same.
Observe that the position of nodes in a tree does not matter as long as the inorder
traversal remains the same. We have seen this in the above figure where two
different trees give the same inorder traversal. In the same way we can insert
more nodes to the tree. After inserting a node we will check the balance of nodes
whether it violates the AVL condition. If the tree, after inserting a node,
becomes unbalance then we will apply rotation to make it balance. In this
way we can build a tree of any number of nodes.
Lecture No. 21
Let’s insert few more nodes in the tree. We will build an AVL tree and rotate the
node when required to fulfill the conditions of an AVL tree.
To insert a node containing number 4, we will, at first, compare the number inside
the Root node. The current root node is containing number 2. As 4 is greater than
2, it will take the right side of the root. In the right subtree of the root, there is
the node containing number 3. As 4 is also greater than 3, it will become the right
child of the node containing number 3.
Once we insert a node in the tree, it is necessary to check its balance to see whether
it is within AVL defined balance. If it is not so, then we have to rotate a
node. If the balance factor of each node inside the tree is 0, it will be a perfectly
balanced tree.
Now, we have inserted a node containing number 5 and see the balance factor of
each node. The balance factor for the node containing 5 is 0. The balance
factor for node containing 4 is –1 and for the node containing 3 is -2. The
condition for AVL is not satisfied here for the node containing number 3, as
its balance factor is –2. The rotation operation will be performed here as with
the help of an arrow.
1 2 3 4 5 6 7
It is still in the same sequence and the number 7 has been added at the end. Now
insert a new node 16 in the tree.
Now, let’s compute the balance factors for the nodes. The balance factor for nodes
16, 7, 5, 3, 1, 6, 2 and 4 is either 0 or –1. So this fulfills the condition of a tree to be
an AVL. Let’s insert another node containing number 15 in this tree.
Now node 7 has become the left child of node 16 while node 15 has attained the
form of the right child of node 7. Now the balance factors for node 15, 7 and 16 are
0, -1 and 2 respectively. Note that the single rotation above when we rotated
node 7 is not enough as our tree is still not an AVL one.
Let us revisit the rotationsLet us call the node that must be rebalanced α.
Since any node has at most two children, and a height imbalance
requires that α’s two subtrees differ by two (or –2), the violation will occur in
four cases.
Insertion occurs on the “inside” in cases 2 and 3 which single rotation cannot
fix.
This is an inside insertion. The balance factor for the node k2 became
2. We make single rotation by making right rotation on the node k2 as shown in
the figure on the right. We compute the balance factor for k1, which is –2. So the
tree is still not within the limits of AVL tree. Primarily the reason for this
failure is the node Y subtree, which is unchanged even after making one
rotation. It changes its parent node but its subtree remains intact.
Lecture No. 22
Cases of rotations
As We have analyzed the insertion method again and talked about the α node. The
new node will be inserted at the left or right subtree of the α’s left child or at the
left or right subtree of the α’s right child. Now the question arises whether the
single rotation help us in balancing the tree or not.
If the new node is inserted in the left subtree of the α’s left child or in the right
subtree of α’s right child, the balance will be restored through single
rotation. However, if the new node goes inside the tree, the single rotation
is not going to be successful in balancing the tree. We face four scenarios in this
case. We said that in the case-1 and case-4, single rotation is successful
while in the case-2 and case-3 single rotation does not work.
In the above tree, we have α node as k2, which has a left child as k1. Whereas X
and Y are its left and right children. The node k2 has a right child Z. Here the
newly inserted node works as the left or right child of node Y. Due to his
insertion, one level is increased in the tree. We have applied single rotation
on the link of k1 and k2. The right side tree in the figure is the post-rotation
tree. The node k1 is now at the top while k2 comes down and node Y changes its
position. Now if you see the levels of the node, these are seen same. Have a look
on the level of the α node i.e. k1 which reflects that the difference between the
left and right side levels is still 2. So the single rotation does not work here.
View the entire tree with four subtrees connected with 3 nodes
Y is non-empty because the new node was inserted in Y.
Here exactly one of tree B or C is two levels deeper than D; we are not sure
which one.
Now, we have the final shape of the tree. You can see that k2 has become
the root of the tree. k1 and k3 are its left and right children respectively.
While performing the inorder traversal, you can see that we have preserved
our inorder traversal. We inserted numbers in it.
When the balance factor becomes more than one, rotation is performed.
During this process, we came at a point when single rotation failed to
balance the tree. Now there is need to perform double rotation to balance the
tree that is actually two single rotations. Do not take double rotation as some
complex function, it is simply two single rotations in a special order. This
order depends on the final position of the new node. Either the new node is
inserted at the right subtree of the left child of α node or at the left subtree of
the right child of α node. In first case, we have to perform left right rotation
while in the second case, the right-left rotation will be carried out.
.
Let’s go back to our example and try to complete it. So far, we have 1, 2, 3,
4, 5, 6, 7 and 16 in the tree and inserted 15 which becomes the left child of
the node 16.