Sunteți pe pagina 1din 115

ITSE205

ITSE205- Data Structures and Algorithms P a g e 1 | 115


Table of Contents
Course Overview
A. Course Objectives
B. Course Outcomes
C. Reference Books
Topics Page No
Chapter 1 – Fundamentals and Algorithm Analysis 5
1.0 Data structures 5
1.1 Abstract data type 6
1.2 Algorithm 7
1.3 Asymptotic analysis 8
1.4 Best, Worst and Average cases 9
1.5 Calculating running time of a program 12
1.6 Big-Oh notation 13
1.7 Omega notation 16
1.8 Theta Notation 17
1.9 Row and column major order 17
Exercises
Chapter 2 – Stacks, Queues and Applications 20
2.0 Stacks 20
2.1 Stack abstract data type 22
2.2 Array based implementation of stacks 23
2.3 Mathematical Expressions 28
2.4 Postfix Evaluation 32
2.5 Queues, applications and implementation 33
2.6 Circular queues 41
2.7 Double-ended queue 43
Exercises
Chapter 3 – Linked Lists 45
3.0 Introduction 45
3.1 Linked list Vs Array 46
3.2 Applications of Linked List 46
3.3 Singly linked list 46
3.4 Implementing Singly linked list 48
3.5 Linked stack 52
3.6 Linked Queue 55
3.7 Circular linked list 58
3.8 Doubly linked list 59
Exercises
Chapter 4 – Sorting and Searching Techniques 63
4.0 Linear search algorithm 63
4.1 Binary Search algorithm 64
4.2 Selection sort algorithm 65
4.3 Bubble sort algorithm 67
4.4 Insertion sort algorithm 69
4.5 Merging two sorted arrays 73
4.6 Merge sort using divide and conquer 75
4.7 Quick sort algorithm 77
4.8 Radix sort algorithm 80
ITSE205- Data Structures and Algorithms P a g e 2 | 115
4.9 Hashing 81
Exercises
Chapter 5 – Trees 88
5.0 Introduction 88
5.1 Why trees 90
5.2 Binary trees 91
5.3 Binary tree traversal 93
5.4 Binary search tree 96
5.5 Heap sort 101
Exercises
Chapter 6 – Graphs 103
6.0 Introduction 103
6.1 Graph notations 104
6.2 Directed and undirected graphs 105
6.3 Graph Traversals 106
6.3.1 Depth first search
6.3.2 Breadth first search
Exercises

ITSE205- Data Structures and Algorithms P a g e 3 | 115


Course Objectives:
The course shall enable the student to
 Analyse the complexity of an algorithm
 Implement stacks, queues and linked list data structures
 Apply sorting and searching techniques
 Understand the concepts of trees and apply tree traversals
 Understand the concepts of graphs and use graph traversals

Course Outcomes:

At the end of the course the student should be enable to:


1. Use arrays, Pointers, Structures and Abstract data types.
2. Discuss Big Oh, Theta and Omega notations.
3. Apply Big Oh to calculate complexities of algorithms.
4. Implement Linear list and single, circular and doubly linked lists.
5. Implement stacks, queues and tables using linear and linked
representation.
6. Use Linear and Binary Search.
7. Apply operations on Trees such as traversal (Pre-order, In-order and
Post-Order) searching, insertion, updating and deletion.
8. Construct Binary Tree and Binary Search Tree (BST)
9. Use Insertion, Selection, Bubble, Quick, Merge, Radix sorting.
10. Construct Graphs
11. Implement hashing techniques
12. Use various algorithms to perform operations such as insertion,
searching, updating and deletion on various data structures.
13. Implement data structures.

Reference Books:

1. Lipschutz. S, Datastructures Tata Mc-Graw Hill, Schaum’s Outliones, 2008


2. Langsam. Y.M. Augenstin and A.Tanenbaum, Data Structures unsing C
and C++, Pearson Education Asia.
3. Michel. T. Goodrich, Data Structures and Algorithms in C++, John Wiley &
Sons
4. Adam Drozdek, Data Structures and Algorithms in C++, Thomson
Learning
5. Ellis horowitz, sartaj sahani, Computer Algorithms, Computer Science
press
6. Mark Allen Weiss, Data Structures and Algorithms in C++, Pearson
Education
7. Herbert Schieldt, C++ Complete Reference

ITSE205- Data Structures and Algorithms P a g e 4 | 115


Chapter #1: Fundamentals and Algorithm Analysis
Objectives:
At the end of this chapter students will be able to:
 Understand the concepts of data structures
 Define asymptotic notation
 Estimate running time of an algorithm

1.0 Data structures


Data structure means how the data is organized in memory. Data Structure
is a way of collecting and organizing data in such a way that we can perform
operations on these data in an effective way. Data Structures is about
rendering data elements in terms of some relationship, for better organization
and storage.
For example, we have data player's name "Ahmed" and age 24. Here
"Ahmed" is of String data type and 24 is of integer data type.
We can organize this data as a record like Player record. Now we can
collect and store player's records in a file or database as a data structure.
For example: "Rashid" 26, "Omar" 24, "Khalfan" 23
In simple language, Data Structures are structures programmed to store
ordered data, so that various operations can be performed on it easily.
Data structure = structured data + allowed operations.

This three-step approach to selecting a data structure operationalizes a data


centered view of the design process. The first concern is for the data and the
operations to be performed on them, the next concern is the representation
for those data, and the final concern is the implementation of that
representation.
Data structures can be classified in to two
types:
 Primitive data structures
 Non-primitive data structures.

Primitive Data Structures are the basic


data structures that directly operate upon
the machine instructions.
Linear data structures are those which
show the relationship of adjacency.
Non-linear data structures are those that
don’t show relationship of adjacency. Non-
linear data structure can be constructed as
ITSE205- Data Structures and Algorithms P a g e 5 | 115
a collection of randomly distributed set of data item joined together by using
a special pointer (tag).
Non-primitive data types are not defined by the programming language, but
are instead created by the programmer. The data types that are derived from
primary data types are known as non-Primitive data types. These datatypes
are used to store group of values.
The non-primitive data types are
Arrays, Structure, Union, linked list, Stacks, Queue etc

Importance of Data Structures:


Data structure is a particular way of storing and organizing information in
a computer so that it can be retrieved and used most productively. Different
kinds of data structures are meant for different kinds of applications, and
some are highly specialized to specific tasks.
Data structures are important for the following reasons:
1. Data structures are used in almost every program or software system.
2. Specific data structures are essential ingredients of many efficient
algorithms, and make possible the management of huge amounts of data,
such as large integrated collection of databases.
3. Some programming languages emphasize data structures, rather than
algorithms, as the key organizing factor in software design.

1.1 Abstract Data type


An abstract data type is a programming language facility for organizing
programs into modules using criteria that are based on the data structures of
the program.
The specification of the module should provide all information required for
using the type, including the allowable values of the data and the effects of
the operations. However, details about the implementation, such as data
representations and algorithms for implementing the operations, are hidden
within the module. This separation of specification from implementation is a
key idea of abstract data types.
An ADT may be defined as a "class of objects whose logical behavior
is defined by a set of values and a set of operations".
An abstract data type (ADT) is basically a logical description or a specification
of components of the data and the operations that are allowed, that is
ITSE205- Data Structures and Algorithms P a g e 6 | 115
independent of the implementation. ADTs are a theoretical concept in
computer science, used in the design and analysis of algorithms, data
structures, and software systems, and do not correspond to specific features
of computer languages.
The C++ class allows for the implementation of ADTs, with appropriate hiding
of implementation details. Thus, any other part of the program that needs to
perform an operation on the ADT can do so by calling the appropriate method.
If for some reason implementation details need to be changed, it should be
easy to do so by merely changing the routines that perform the ADT
operations.
Examples of ADT are: stack, queue and list
An abstract data type (ADT) is the realization of a data type as a
software component. The interface of the ADT is defined in terms of a type
and a set of operations on that type. The behaviour of each operation is
determined by its inputs and outputs. An ADT does not specify how the data
type is implemented. These implementation details are hidden from the user
of the ADT and protected from outside access, a concept referred to as
encapsulation.
A data structure is the implementation for an ADT. In an object-oriented
language such as C++, an ADT and its implementation together make up a
class. Each operation associated with the ADT is implemented by a member
function or method. The variables that define the space required by a data
item are referred to as data members. An object is an instance of a class, that
is, something that is created and takes up storage during the execution of a
computer program.

1.2 What is an Algorithm


An algorithm is a finite set of instructions that, if followed, accomplishes a
particular task. All algorithms must satisfy the following criteria:
1. Input: Zero or more quantities are externally supplied.
2. Output: At least one quantity is produced.
3. Definiteness: Each instruction is clear and unambiguous.
4. Finiteness: If we trace out the instructions of an algorithm, then for all
cases, the algorithm terminates after a finite number of steps.

ITSE205- Data Structures and Algorithms P a g e 7 | 115


5, Effectiveness: Every instruction must be very basic so that it can be
carried out, in principle, by a person using only pencil and paper. It is not
enough that each operation be definite as in criterion3; it must be feasible.
1.3 Asymptotic Analysis
Algorithmic complexity is a very important topic in computer science.
Knowing the complexity of algorithms allows you to answer questions such
as
 How long will a program run on an input?
 How much space will it take?
 Is the problem solvable?
The purpose of asymptotic analysis is:
 To estimate how long a program will run.
 To estimate the largest input that can reasonably be given to the
program.
 To compare the efficiency of different algorithms.
 To help focus on the parts of code that are executed the largest
number of times.
 To choose an algorithm for an application.
These are important bases of comparison between different algorithms. An
understanding of algorithmic complexity provides programmers with insight
into the efficiency of their code. Complexity is also important to several
theoretical areas in computer science, including algorithms, data structures,
and complexity theory.
Asymptotic analysis of an algorithm refers to defining the mathematical
foundation/framing of its run-time performance. Using asymptotic analysis,
we can very well conclude the best case, average case, and worst case
scenario of an algorithm.
Asymptotic analysis is input bound i.e., if there's no input to the algorithm, it
is concluded to work in a constant time. Other than the "input" all other factors
are considered constant.
Asymptotic analysis refers to computing the running time of any operation in
mathematical units of computation. For example, the running time of one
operation is computed as f(n) and may be for another operation it is
computed as g(n2). This means the first operation running time will increase

ITSE205- Data Structures and Algorithms P a g e 8 | 115


linearly with the increase in n and the running time of the second operation
will increase quadratic when n increases. Similarly, the running time of both
operations will be nearly the same if n is significantly small.
Usually, the time required by an algorithm falls under three types −
Best Case − Minimum time required for program execution.
Average Case − Average time required for program execution.
Worst Case − Maximum time required for program execution.
To be precise, asymptotic analysis refers to the study of an algorithm as the
input size “gets big” or reaches a limit (in the calculus sense).
Asymptotic analysis measures the efficiency of an algorithm, or its
implementation as a program, as the input size becomes large. It is actually
an estimating technique and does not tell us anything about the relative
merits of two programs where one is always “slightly faster” than the other.
However, asymptotic analysis has proved useful to computer scientists who
must determine if a particular algorithm is worth considering for
implementation.
The critical resource for a program is most often its running time. However,
you cannot pay attention to running time alone. You must also be concerned
with other factors such as the space required to run the program (both main
memory and disk space). Typically, you will analyse the time required for an
algorithm (or the instantiation of an algorithm in the form of a program), and
the space required for a data structure.

1.4 Best, Worst, and Average Cases


Consider a simple algorithm ‘largest-value sequential search’ to solve
the problem of finding the largest value in an array of n integers.

Example: Sequential search


// Return position of largest value in "A" of size "n"
int largest(int A[], int n) {
int currlarge = 0; // Holds largest element position

for (int i=1; i<n; i++) // For each array element

if (A[currlarge] < A[i]) // if A[i] is larger

currlarge = i; // remember its position

return currlarge; // Return largest position


}

ITSE205- Data Structures and Algorithms P a g e 9 | 115


Here, the size of the problem is n, the number of integers stored in A. The
basic operation is to compare an integer’s value to that of the largest value
seen so far. It is reasonable to assume that it takes a fixed amount of time to
do one such comparison, regardless of the value of the two integers or their
positions in the array.
Because the most important factor affecting running time is normally size of
the input, for a given input size n we often express the time T to run the
algorithm as a function of n, written as T(n). We will always assume T(n) is
a non-negative value.
Let us call c the amount of time required to compare two integers in function
largest. We do not care right now what the precise value of c might be. Nor
are we concerned with the time required to increment variable i because this
must be done for each value in the array, or the time for the actual assignment
when a larger value is found, or the little bit of extra time taken to initialize
currlarge. We just want a reasonable approximation for the time taken to
execute the algorithm. The total time to run largest is therefore
approximately cn, because we must make n comparisons, with each
comparison costing c time. We say that function largest (and the largest-
value sequential search algorithm in general) has a running time expressed
by the equation T(n) = cn:
This equation describes the growth rate for the running time of the largest
value sequential search algorithm.

Example: Best, Worst, and Average Cases


Consider the problem of sequential search to search one particular value K.
The sequential search algorithm begins at the first position in the array and
looks at each value in turn until K is found. Once K is found, the algorithm
stops. This is different from the largest-value sequential search algorithm.
There is a wide range of possible running times for the sequential search
algorithm. The first integer in the array could have value K, and so only one
integer is examined. In this case the running time is short. This is the best
case for this algorithm. Alternatively, if the last position in the array
contains K, then the running time is relatively long, because the algorithm
must examine n values. This is the worst case for this algorithm.

ITSE205- Data Structures and Algorithms P a g e 10 | 115


If we implement sequential search as a program and run it many times on
many different arrays of size n, or search for many different values of K within
the same array, we expect the algorithm on average to go halfway through
the array before finding the value we seek. On average, the algorithm
examines about n/2 values. We call this the average case for this algorithm.
When analyzing an algorithm, should we study the best, worst, or average
case?
Normally we are not interested in the best case, because this might happen
only rarely and generally is too optimistic for a fair characterization of the
algorithm’s running time.
How about the worst case? The advantage to analyzing the worst case is that
you know for certain that the algorithm must perform at least that well. This
is especially important for real-time applications, such as for the computers
that monitor an air traffic control system. Here, it would not be acceptable to
use an algorithm that can handle n airplanes quickly enough most of the time,
but which fails to perform quickly enough when all n airplanes are coming
from the same direction.
For other applications — particularly when we wish to aggregate the
cost of running the program many times on many different inputs — worst-
case analysis might not be a representative measure of the algorithm’s
performance. Often we prefer to know the average-case running time. This
means that we would like to know the typical behaviour of the algorithm on
inputs of size n. Unfortunately, average-case analysis is not always possible.
Average-case analysis first requires that we understand how the actual inputs
to the program (and their costs) are distributed with respect to the set of all
possible inputs to the program. For example, it was stated previously that the
sequential search algorithm on average examines half of the array values.
This is only true if the element with value K is equally likely to appear in any
position in the array. If this assumption is not correct, then the algorithm does
not necessarily examine half of the array values in the average case.
In summary, for real-time applications we are likely to prefer a worst-
case analysis of an algorithm. Otherwise, we often desire an average-case
analysis if we know enough about the distribution of our input to compute the
average case. If not, then we must resort to worst-case analysis.

ITSE205- Data Structures and Algorithms P a g e 11 | 115


1.5 Calculating running time of a program
Example
Here is a simple program fragment to calculate ∑Ni=1 i3
int sum( int n )
{
int tot;
1 tot = 0;
2 for( int i = 1; i <= n; ++i )
3 tot = tot + i * i * i;
4 return tot;
}
The analysis of this fragment is simple.
The declarations count for no time. Lines 1 and 4 count for one unit each. Line
3 counts for four units per time executed (two multiplications, one addition,
and one assignment) and is executed N times, for a total of 4N units.
Line 2 has the hidden costs of initializing i testing i ≤ N, and incrementing i.
The total cost of all these is 1 to initialize, N+1 for all the tests, and N for all
the increments, which is 2N + 2. We ignore the costs of calling the function
and returning, for a total of 6N + 4.
Thus, we say that this function is O (N) which is Big-Oh notation.
Example:

int sumList(A, n)
{
1 int tot=0;
2 for(int i=0; i<n; i++)
3 tot= tot + A[i];
4 return tot;
}
Line 1 count for one unit.
Line 2 counts for :
Initialization cost is 1,
N+1 for all tests
N for all increments.
Line 3 Count for 2N
Line 4 Count for 1
Total cost: 1 + 1 + N+1 + N+ 2N +1
4N + 4
Thus, we say that this function is O (N).

ITSE205- Data Structures and Algorithms P a g e 12 | 115


1.6 Big-Oh Notation

The function f(n) =O(g(n)) (read as “f of n is big oh of g of n”) iff (if and
only if) there exist positive constants C and no such that f(n) <= c * g(n)
for all n, n>=no.
Big O notation is used in Computer Science to describe the performance or
complexity of an algorithm. Big O specifically describes the worst-case
scenario, and can be used to describe the execution time required or the space
used (e.g. in memory or on disk) by an algorithm.

Some common orders of growth:


O(1) : Constant
O(1) describes an algorithm that will always execute in the same time
(or space) regardless of the size of the input data set.

O(N) : Linear
O(N) describes an algorithm whose performance will grow linearly and
in direct proportion to the size of the input data set.

O(N2) : Quadratic
O(N2) represents an algorithm whose performance is directly
proportional to the square of the size of the input data set. This is common
with algorithms that involve nested iterations over the data set. Deeper
nested iterations will result in O(N3), O(N4) etc.

O(2N) : Exponential
O(2N) denotes an algorithm whose growth doubles with each addition
to the input data set. The growth curve of an O(2 N) function is exponential -
starting off very shallow, then rising meteorically.
An example of an O(2N) function is the recursive calculation of Fibonacci
numbers:
int Fibonacci(int number)
{
if (number <= 1) return number;
return Fibonacci(number - 2) + Fibonacci(number - 1);
}

ITSE205- Data Structures and Algorithms P a g e 13 | 115


Logarithms:
Logarithms are slightly trickier to explain; therefore, a common example is
used here:
Binary search is a technique used to search sorted data sets. It works by
selecting the middle element of the data set, essentially the median, and
compares it against a target value. If the values match it will return success.
If the target value is higher than the value of the probe element it will take
the upper half of the data set and perform the same operation against it.
Likewise, if the target value is lower than the value of the probe element it
will perform the operation against the lower half. It will continue to halve the
data set with each iteration until the value has been found or until it can no
longer split the data set.
This type of algorithm is described as O(log N). The iterative halving of data
sets described in the binary search example produces a growth curve that
peaks at the beginning and slowly flattens out as the size of the data sets
increase e.g. an input data set containing 10 items takes one second to
complete, a data set containing 100 items takes two seconds, and a data set
containing 1000 items will take three seconds. Doubling the size of the input
data set has little effect on its growth as after a single iteration of the
algorithm the data set will be halved and therefore on a par with an input data
set half the size. This makes algorithms like binary search extremely efficient
when dealing with large data sets.

Example 1:
sum = 0;
for( i = 0; i < n; i++)
sum++;
The running time for the operation sum++ is a constant. The loop runs n
times, hence the complexity of the loop would be O(n)

Example 2:
sum = 0;
for( i = 0; i < n; i++)
for( j = 0; j < n; j++)
sum++;

ITSE205- Data Structures and Algorithms P a g e 14 | 115


The running time for the operation sum++ is a constant.
The outer loop runs n times, the nested loop also runs n times, hence the
complexity would be O(n2)
Example 3:
sum = 0;
for( i = 0; i < n; i++)
for( j = 0; j < i; j++)
sum++;
The running time for the operation sum++ is a constant.
The outer loop runs n times. For the first execution of the outer loop the inner
loop runs only once. For the second execution of the outer loop the inner loop
runce twice, for the third execution - three times, etc. Thus the inner loop will
be executed 1 + 2 + ... + (n-1) + n times.
1 + 2 + ... + (n-1) + n = n(n+1) / 2, which gives (n+1) / 2 on average.
Thus the total running time would be O(n*(n+1)/2) = O(n*n) = O(n2)

Example 4:
Suppose that for some algorithm, the exact number of steps is
T(n)=5n2+27n+1005. When n is small, say 1 or 2, the constant 1005 seems
to be the dominant part of the function. However, as n gets larger, the n2
term becomes the most important. In fact, when n is really large, the other
two terms become insignificant in the role that they play in determining the
final result. Again, to approximate T(n) as n gets large, we can ignore the
other terms and focus on 5n2. In addition, the coefficient 5 becomes
insignificant as n gets large. We would say then that the function T(n) has an
order of magnitude f(n)=n2, or simply that it is O(n2).

ITSE205- Data Structures and Algorithms P a g e 15 | 115


Exercise: Prove that the running time for the below following code is 3N+2
void printArray(intarr[], intsize){
for(int i= 0; i< size; ++i){
cout<< arr[i] << endl;
}
}

1.7 Omega Notation, Ω


The notation Ω(n) is the formal way to express the lower bound of an
algorithm's running time. It measures the best case time complexity or the
best amount of time an algorithm can possibly take to complete.
For non-negative functions, f(n) and g(n), if there exists an integer n0 and a
constant c >0 such that for all integers n > n0 , f(n) more than or equal to
c.g(n) , then f(n) is Ω(g(n)). This is denoted as f(n) = Ω(g(n)).

This is almost the same definition as Big Oh, except that f(n) more than or
equal to cg(n) , this makes g(n) a lower bound function, instead of an upper
bound function. It describes the best that can happen for a given data size.

Ω(f(n)) ≥ { g(n) :
there exists c > 0 and n0 such that
g(n) ≤ c.f(n) for all n > n0}

ITSE205- Data Structures and Algorithms P a g e 16 | 115


1.8 Theta Notation, θ
The notation θ(n) is the formal way to express both the lower bound and the
upper bound of an algorithm's running time. It is represented as follows −

θ(f(n)) = {g(n)
if and only if g(n) = Ο(f(n)) and g(n) = Ω(f(n))
for all n > n0}

1.9 Row and Column Major order in Arrays


In computing, row-major order and column-major order are methods for
storing multidimensional arrays in linear storage such as random access
memory.

The difference between the orders lies in which elements of an array are
contiguous in memory. In a row-major order, the consecutive elements of a
row reside next to each other, whereas the same holds true for consecutive
elements of a column in a column-major order

Let’s say we have one dimensional array having elements like this:
A1, A2, A3…….An
These elements are stored in my linear memory space as follows:
A1 A2 A3 A4 …..... An
One thing you need to remember is, no matter what type of array it is (1D or
multi-dimensional), they will be always stored linearly in the memory space
as above.
Let’s jump to the case of multi-dimensional arrays now. Imagine a 3×3
matrix like this:

A11 A12 A13


A21 A22 A23
A31 A32 A33

Row Major Order is a method of representing multi dimension array in


sequential memory. In this method elements of an array are arranged
ITSE205- Data Structures and Algorithms P a g e 17 | 115
sequentially row by row. Thus elements of first row occupies first set of
memory locations reserved for the array, elements of second row occupies
the next set of memory and so on.
Consider a Two Dimensional Array consist of N rows and M columns.
It can be stored sequentially in memory row by row as shown below:

Example:
Consider following example in which a two dimensional array consist of two
rows and four columns is stored sequentially in row major order as:

The Location of element A[i, j] can be obtained by evaluating expression:


LOC (A [i, j]) = Base_Address + W [M (i) + (j)]
Here,
Base_Address is the address of first element in the array.
W is the word size. It means number of bytes occupied by each element.
N is number of rows in array.
M is number of columns in array.
Suppose we want to calculate the address of element A [1, 2].

ITSE205- Data Structures and Algorithms P a g e 18 | 115


It can be calculated as follow:
Here,
Base_Address = 2000, W= 2, M=4, N=2, i=1, j=2
LOC (A [i, j]) = Base_Address + W [M (i) + (j)]
LOC (A[1, 2]) = 2000 + 2 *[4*(1) + 2]
= 2000 + 2 * [4 + 2]
= 2000 + 2 * 6
= 2000 + 12
= 2012

ITSE205- Data Structures and Algorithms P a g e 19 | 115


Chapter #2 Stacks, Queues and Applications
Objectives:
At the end of this chapter students will be able to:
 Identify the operations of stacks and queues
 Implement stacks and queues
 Use applications of stacks and queues

2.0 Stacks
Stack is a non-linear data structure that allows insertion and deletion to be
made at only at one end.
Stack is an ordered group of homogeneous items of elements. In stack,
elements are added to and removed from the top of the stack (the most
recently added items are at the top of the stack). The last element to be added
is the first to be removed (LIFO: Last In First Out list). It is also called as
FILO (First in Last Out).

Two basic operations of a stack are Push and Pop. Inserting an element
is called as Push. Deleting a topmost element is called as pop. Elements are
inserted and deleted from only one end.
Stacks can be implemented using:
 Static Implementation (Using Arrays)
 Dynamic Implementation (Using Linked List)
They are used in many applications, including the following.
1: Internet Web browsers store the addresses of recently visited sites on a
stack. Each time a user visits a new site, that site’s address is “pushed” onto
the stack of addresses. The browser then allows the user to “pop” back to
previously visited sites using the “back” button.
2: Text editors usually provide an “undo” mechanism that cancels recent
editing operations and reverts to former states of a document. This undo
operation can be accomplished by keeping text changes in a stack.

ITSE205- Data Structures and Algorithms P a g e 20 | 115


3: Balancing Symbols: Compilers check your programs for syntax errors, but
frequently a lack of one symbol (such as a missing brace or comment starter)
can cause the compiler to spill out a hundred lines of diagnostics without
identifying the real error. To check that every right brace, bracket, and
parentheses must correspond to its left counterpart e.g. [( )] is legal, but [( ]
) is illegal.
4: The C++ run-time system keeps track of the chain of active functions
with a stack. When a function is called, the runtime system pushes on the
stack a frame containing:
• Local variables and return value
• Program counter, keeping track of the statement being executed
• When a function returns, its frame is popped from the stack and control is
passed to the method on top of the stack.
5: Evaluating Postfix: Another problem related to the manipulation of
expressions is the transformation of expressions from infix notation to postfix
notation. In infix notation the operations of the expressions are placed in
between the operands.
6: Infix to postfix conversion.
7: Backtracking (game playing, finding paths, exhaustive searching)
Backtracking is used in algorithms in which there are steps along some path
(state) from some starting point to some goal.
•Find your way through a maze.
•Find a path from one point in a graph (roadmap) to another point.
•Play a game in which there are moves to be made (checkers, chess).
In all of these cases, there are choices to be made among a number of options.
We need some way to remember these decision points in case we want/need
to come back and try the alternative. Consider the maze. At a point where a
choice is made, we may discover that the choice leads to a dead-end. We
want to retrace back to that decision point and then try the other (next)
alternative.
Again, stacks can be used as part of the solution.
8: Recursion is another, typically more favoured, solution, which is actually
implemented by a stack.

ITSE205- Data Structures and Algorithms P a g e 21 | 115


2.1 The Stack Abstract Data Type
Stacks are the simplest of all data structures, yet they are also among the
most important, since they are used in a host of different applications that
include many more sophisticated data structures. Formally, a stack is an
abstract data type (ADT) that supports the following operations:
 push(e): Insert element e at the top of the stack.
 pop(): Remove the top element from the stack; an error occurs if the
stack is empty.
 top(): Return a reference to the top element on the stack, without
removing it; an error occurs if the stack is empty.
Additionally, let us also define the following supporting functions:
 size(): Return the number of elements in the stack.
 empty(): Return true if the stack is empty and false otherwise.

Example: The below table illustrates the stack operations and their effects.
Initially the stack is empty.
Operation Output Stack
contents
pop() Error –
(No elements in stack)
push(7) - [7]
push(2) - [7,2]
push(4) - [7,2,4]
pop() 4 [7,2]
Pop() 2 [7]
Push(3) - [7,3]
Push(1) - [7,3,1]
Empty() False [7,3,1]
Size() 3 [7,3,1]
Top() 1 [7,3,1]
Pop() 1 [7,3]
Top() 3 [7,3]
Pop() 3 [7]
Pop() 7 []
Empty() True []
Top() Error – []
(No elements in stack)
Pop() Error – []
(No elements in stack)

ITSE205- Data Structures and Algorithms P a g e 22 | 115


2.2 Array Based Implementation of Stack
Associated with a stack there will be a TOP pointer. Initially TOP is assigned
to -1 (means no elements in the stack). For every push operation TOP will be
incremented by 1 and for every pop operation TOP will be decremented by 1.

Push operation:

void Push( int x);


Push an element onto the stack. In order to perform ‘push’ operation, first
we need to check whether the stack is full or not.

if (top == size-1) If the stack is full, print the error


print “Stack Over flow” information.
else Note top always represents the
top ++; // Increment top; index of the top element.
stack[top] = x; // store the value. Increment top and push the
element.

Pop operation: returns the topmost element of from the stack


int pop()
int pop()  Pop and return the element
{ at the top of the stack
 If the stack is empty, print
if (top<0) return -1;
the error information. (In this
//or print error message “underflow” case, the return value is
int t = stack[top]; useless.)
 Otherwise,
// store topmost value
Take the topmost element,
top--; // decrement top position decrement top by 1, return
return t; // return the popped value popped valued.

ITSE205- Data Structures and Algorithms P a g e 23 | 115


Method 1: Sample Code (Methods not returning values)

ITSE205- Data Structures and Algorithms P a g e 24 | 115


ITSE205- Data Structures and Algorithms P a g e 25 | 115
Method 2: Sample Code (introducing isEmpty() and isFull() methods)

ITSE205- Data Structures and Algorithms P a g e 26 | 115


ITSE205- Data Structures and Algorithms P a g e 27 | 115
2.3 Mathematical Expressions
An expression is defined as the number of operands or data items combined
with several operators. Expression can be represented in three forms (Infix,
Postfix and Prefix). We can also convert one type of expression to another
type of expression like Infix to Postfix, Infix to Prefix, Postfix to Prefix and vice
versa.
There are basically three types of notations for an expression;
1) Infix notation
2) Prefix notation
3) Postfix notation
Infix notation: It is most common notation in which, the operator is
written or placed in-between the two operands. For example, the expression
to add two numbers A and B is written in infix notation as A + B.

Prefix Notation: It is also called Polish notation, refers to the notation in


which the operator is placed before the operand as: +AB
As the operator ‘+’ is placed before the operands A and B, this notation is
called prefix (pre means before).
Postfix Notation: In the postfix notation the operators are written after the
operands, so it is called the postfix notation (post means after), it is also
known as suffix notation or reverse polish notation. The above postfix if
written in postfix notation looks like follows: AB+

To convert any Infix expression into Postfix or Prefix expression we can use
the following procedure...
1. Find all the operators in the given Infix Expression.
2. Find the order of operators evaluated according to their Operator
precedence.
3. Convert each operator into required type of expression (Postfix or
Prefix) in the same order.

ITSE205- Data Structures and Algorithms P a g e 28 | 115


Example:
Consider the following Infix Expression to be converted into Postfix
Expression...
A+B*C
Step 1: The Operators in the given Infix Expression +,*
Step 2: The Order of Operators according to their preference * , +
Step 3: Now, convert the first operator * --------- A + B C *
Step 4: Convert the next operator + --------------- A BC* +
Finally, given Infix Expression is converted into
Postfix Expression as follows A B C * +

The need to convert infix into prefix or postfix


Infix notation is easy to read for humans, whereas prefix/postfix notation is
easier to parse for a machine. The big advantage in prefix/postfix notation is
that there never arise any questions like operator precedence.
For example, consider the infix expression 1 # 2 $ 3. Now, we don't know
what those operators mean, so there are two possible corresponding postfix
expressions: 1 2 # 3 $ and 1 2 3 $ #.
Without knowing the rules governing the use of these operators, the infix
expression is essentially worthless.
If a compiler allowed infix expressions into the binary code used in the
compiled version of a program, the resulting code would be larger the needed
and very inefficient. This is the reason, the compiler convert the infix
expression into a postfix expression. Postfix is very easy to process left-to-
right. An operand is pushed onto a stack; an operator pops its operand(s)
from the stack and pushes the result. Little or no parsing is necessary. It's
used by some calculators (HP calculators are noted for using RPN).
In more general terms:
It is possible to restore the original (parse) tree from a prefix/postfix
expression without any additional knowledge, but the same isn't true for infix
expressions.

ITSE205- Data Structures and Algorithms P a g e 29 | 115


Three Conversion rules:
1. Operator Priority
How do you figure out the operands of an operator?
a + b * c
a * b + c / d
This is done by assigning operator priorities.
priority(*) = priority(/) more than priority(+) = priority(-)
When an operand lies between two operators, the operand associates with
the operator that has higher priority.

2. Tie Breaker
When an operand lies between two operators that have the same priority,
the operand associates with the operator on the left.
a + b - c
a * b / c / d

3. Delimiters
Sub expression within delimiters is treated as a single operand, independent
from the remainder of the expression.
(a + b) * (c – d) / (e – f)

Example: Convert the following expression from infix to postfix.


Infix: A+B*C

A+B*C = A+BC* First priority to *


= A+ D Let D =BC*
= AD+
=> ABC*+ postfix notation
Examples:

Infix Postfix
(A+B)/D AB+D/
(A+B) / (D+E) AB+DE+/
(A-B/C +E) / (A+B) ABC/-E+AB+/
A*(B+C)/D ABC+*D/
( A + B - C ) * D – ( E + F ) AB + C – D * E F + -
2-3*4+5 234*-5+
(2-3)*(4+5) 23-45+*
2-(3*4+5) 234*5+-
3+4*5/6 345*6/+
3*2+25/5 32*25 5 /+

ITSE205- Data Structures and Algorithms P a g e 30 | 115


Example: Convert the infix expression A+B*C-D into postfix.
A+B*C-D => A + BC* - D
=> ABC*+ - D
=> ABC*+D- Postfix notation.

Illustration Using Stacks:

ITSE205- Data Structures and Algorithms P a g e 31 | 115


2.4 Postfix Evaluation

The algorithm for the conversion is as follows:


 Evaluation algorithm:
 Use stack of tokens
 Repeat
o Read token
o If operand, push onto stack
o If operator
 pop operands off stack
 evaluate operator on operands
o push result onto stack
 Until expression is read
 Return top of stack

Example: Evaluate the following Postfix string 1 2 3 * + 4 –

Initially the Stack is empty.

ITSE205- Data Structures and Algorithms P a g e 32 | 115


Example: Evaluate the following Postfix string 4 5 + 7 2 - *

2.5 Queues
Data structure in which the elements are added at one end, called the rear,
and deleted from the other end, called the front. A queue is a First In First
Out(FIFO) data structure.
Insertions are made at one end (rear) whereas deletions are made at other
end (front). Insertion at rear end also called as enqueue. Deletion at front
end also referred as dequeue.

Fundamental Operations
 Insert element into Queue
 Delete element from Queue
 Print Queue elements
Other Operations
 Get front element
 Get Rear element
ITSE205- Data Structures and Algorithms P a g e 33 | 115
Applications:
1. Servicing request of a single shared resource (printer, disk, cpu)
2. Asynchronous transmission of data (data not necessarily received at the
same rate as sent) between two processes (IO buffers).
Example: pipes, file IO, sockets
3. Call center phone system which will use queue to hold people in line until
service representative is free.
4. Computer systems must often provide a “holding area” for messages
between two processes, two programs, or even two systems. This holding
area is usually called a “buffer” and is often implemented as a queue
5. Round robin technique for processor scheduling.
Three major variations of Queues are:
1. Circular queue
2. Double ended queue (de-queue)
3. Priority queue

ITSE205- Data Structures and Algorithms P a g e 34 | 115


The queue abstract data type (ADT) supports the following operations:
 enqueue(e): Insert element e at the rear of the queue.
 dequeue(): Remove element at the front of the queue; an error
occurs if the queue is empty.
 front(): Return, but do not remove, a reference to the front element
in the queue; an error occurs if the queue is empty.
The queue ADT also includes the following supporting member functions:
 size(): Return the number of elements in the queue.
 empty(): Return true if the queue is empty and false otherwise.

enqueue and dequeue operations in a Queue:

ITSE205- Data Structures and Algorithms P a g e 35 | 115


Example: The below table illustrates the queue operations and their effects.
Initially the queue is empty.

Operation Output Front-Queue-Rear


dequeue() error []
enqueue(7) - [7]
enqueue(4) - [7,4]
enqueue(5) - [7,4,5]
size() 3 [7,4,5]
empty() false [7,4,5]
dequeue() 7 [4,5]
enqueue(9) - [4,5,9]
dequeue() 4 [5,9]
size() 2 [5,9]
front() 5 [5,9]
dequeue() 5 [9]
dequeue() 9 []
dequeue() error []
empty() true []
size() 0 []

enQueue(value) - Inserting value into the queue


In a queue data structure, enQueue() is a function used to insert a new
element into the queue. In a queue, the new element is always inserted at
rear position. The enQueue() function takes one integer value as parameter
and inserts that value into the queue.
We can use the following steps to insert an element into the queue...
Step 1: Check whether queue is FULL. (rear == SIZE-1)
Step 2: If it is FULL, then display "Queue is FULL Insertion is not
possible" and terminate the function.
Step 3: If it is NOT FULL,
A. if front = rear =-1 (First insertion)
then increment front and rear by one and queue[rear]=value.
B. if front != rear
then increment rear value by one (rear++)
and set queue[rear] = value.

ITSE205- Data Structures and Algorithms P a g e 36 | 115


deQueue() - Deleting a value from the Queue
In a queue data structure, deQueue() is a function used to delete an element
from the queue. In a queue, the element is always deleted from front
position. The deQueue() function does not take any value as parameter. We
can use the following steps to delete an element from the queue...
Step 1 − Check if the queue is empty [front=-1].
Step 2 − If the queue is empty, produce underflow error and exit.
Step 3 − If the queue is not empty, access the data where front is pointing.
Then check whether both front and rear are equal (front == rear), if it
TRUE, then set both front and rear to '-1' (front = rear = -1).
Step 4 − Increment front pointer to point to the next available data element.

display() - Displays the elements of a Queue


We can use the following steps to display the elements of a queue...
Step 1: Check whether queue is EMPTY (front = -1).
Step 2: If it is EMPTY, then display "Queue is EMPTY!!" and terminate the
function.
Step 3: If it is NOT EMPTY, then define an integer variable 'i' and set 'i =
front'.
Step 3: Display 'queue[i]' value and increment 'i' value by one (i++). Repeat
the same until 'i' value is equal to rear (i <= rear)

Implementation of Queues using arrays:


#include <iostream>
using namespace std;
class que
{
private:
int siz;
int *q;
int fr,rr;
public:
que()
{
fr = rr = -1;
}
void init()
{
cout<<endl<<"Enter queue size ";
cin>>siz;
q = new int[siz];
}

ITSE205- Data Structures and Algorithms P a g e 37 | 115


void enqueue(int a);
int dequeue();
void display();
void menu();
bool isfull()
{
if (rr == siz-1)
return true;
return false;
}
bool isempty()
{
if (fr == -1)
return true;
return false;
}
};
void que::enqueue(int a)
{
if(isfull())
cout<<endl<<"\t Queue overflow ";
else
{
if(fr==-1)
fr=rr=0;
else
rr++;
q[rr]=a;
cout<<endl<<"\t Insert successful ";
}
}
int que::dequeue()
{
int t;
if(isempty())
return -1;

t=q[fr];
if(fr==rr)
fr=rr=-1;
else
fr++;
return t;
}
void que::display()
{
if(fr == rr)
cout<<endl<<"\t Queue is Empty ";
else
{
int k;
cout<<endl<<"\t Queue elements are "<<endl;
for(k=fr; k<=rr; k++)
cout<<"\t"<<q[k];
}

ITSE205- Data Structures and Algorithms P a g e 38 | 115


}
void que::menu()
{
cout<<endl<<"\t Queue Operations ";
cout<<endl<<"\t 1. Insert element to queue ";
cout<<endl<<"\t 2. Delete element from queue ";
cout<<endl<<"\t 3. Display queue elements ";
cout<<endl<<"\t Select any one choice ";
}
int main()
{
que *obj;
obj = new que();
obj->init();

int val, temp,choice;


char ch;
do
{
obj->menu();
cin>>choice;
switch(choice)
{
case 1:
cout<<endl<<"\n enter element to insert ";
cin>>val;
obj->enqueue(val);
break;
case 2:
temp = obj->dequeue();
if (temp==-1)
cout<<endl<<"Queue under flow";
else
cout<<endl<<" Deleted element is "<<temp;
break;
case 3:
obj->display();
break;
default:
cout<<endl<<"\t wrong choice ";
}
cout<<endl<<" To test again press y or Y ";
cin>>ch;
}while((ch=='y')||(ch=='Y'));

cout<<endl<<" Thanks for using Queues ";


return 0;
}

ITSE205- Data Structures and Algorithms P a g e 39 | 115


ITSE205- Data Structures and Algorithms P a g e 40 | 115
2.6 Circular Queues
Let us consider the following queue

The drawback of this queue is:


enqueue(13) -> will generate “Queue over flow” message.
But there are empty locations in the queue and we can’t store any values.
In order to overcome this drawback, a queue can be
converted as a circular queue.
In a standard queue data structure re-buffering
problem occurs for each dequeue operation. To
solve this problem by joining the front and rear ends
of a queue to make the queue as a circular queue
Circular queue is a linear data structure.
It follows FIFO principle.
 In circular queue the last node is connected back to the first node to
make a circle.
 Circular linked list fallow the First In First Out principle
 Elements are added at the rear end and the elements are deleted at
front end of the queue
 Both the front and the rear pointers points to the beginning of the
array.
 It is also called as “Ring buffer”.

ITSE205- Data Structures and Algorithms P a g e 41 | 115


ITSE205- Data Structures and Algorithms P a g e 42 | 115
2.7 Double-ended queue:

A deque, also known as a double-ended queue, is an ordered collection of


items similar to the queue. It has two ends, a front and a rear, and the items
remain positioned in the collection.
What makes a deque different is the unrestrictive nature of adding and
removing items. New items can be added at either the front or the rear.
Likewise, existing items can be removed from either end.
In a sense, this hybrid linear structure provides all the capabilities of stacks
and queues in a single data structure.

ITSE205- Data Structures and Algorithms P a g e 43 | 115


Exercises:
1. Develop an application to read one string and print in reverse order using
stacks.
2. Develop an application to implement postfix evaluation using stacks.
3. Implement a stack ADT. (Implement stack using templates that supports
integers/floats/characters).
4. Implement a queues using templates that supports
integers/floats/characters
5. Develop a menu based application that implements ‘enqueue, dequeue and
display’ operations of a circular queue.

ITSE205- Data Structures and Algorithms P a g e 44 | 115


Chapter #3 – Linked List
Objectives
At the end of this chapter students will be able to:
 Understand the concepts and applications of linked list
 Differentiate between array and linked list
 Identify types of linked list
 Implement linked list
 Apply linked list for a given problem

3.0 Introduction
A linked list is a data structure consisting of a group of nodes which together
represent a sequence.
In simplest form,
Each node is composed of a data and a reference (in other words, a
link) to the next node.

A linked list is a linear collection of data elements, called nodes, linked to one
another by means of pointers.
Each node is divided into two parts: the first part contains the information of
the element, and the second part contains the address of the next node in the
linked list. Address part of the node is also called linked or next field or Link
field.
Each element (we will call it a node) of a list is comprising of two items - the
data and a reference to the next node.
The last node has a reference to null.
The entry point (first node) into a linked list is called the Root or head or
front of the list.
Two basic Types of Linked Lists
1. Singly Linked List
2. Doubly Linked List
We can make as a Circular Linked List

ITSE205- Data Structures and Algorithms P a g e 45 | 115


3.1 Linked List Vs Array
Arrays - disadvantage
 The size of the arrays is fixed:
 Inserting a new element in an array of elements is expensive
 Deletion is also expensive with arrays
Linked list provides following four advantages
1) Dynamic size
2) Ease of insertion/deletion
3) Efficient memory utilization
4) Many complex applications can be easily carried out.
Linked lists have following drawbacks:
1) Random access is not allowed. We have to access elements sequentially
starting from the first node.
2) Extra memory space for a pointer is required to link with next element of
the list.
3) Extra time required. Access to an arbitrary data item is little bit
cumbersome and also time consuming.

3.2 Applications of Linked List


 Representing polynomials
 In dynamic memory management
 In symbol tables
 Representing sparse matrix
 The cache in your browser that allows you to hit “BACK” button.
(linked list of URLs)
 “UNDO” functionality
 Stack, Queue, hash table, binary tree

3.3 Singly Linked List


 Data structure consisting of sequence of nodes.
 Each node consists of
 Data
 And Link to next node

ITSE205- Data Structures and Algorithms P a g e 46 | 115


Operations on Linked List
 Creation
Creation operation is used to create a linked list. Once a linked list is
created with one node, insertion operation can be used to add more elements
in a node.
 Insertion
Insertion operation is used to insert a new node at any specified
location in the linked list. A new node may be inserted.
 Insertion at the Beginning

ITSE205- Data Structures and Algorithms P a g e 47 | 115


 Insertion in any specified position
 Insertion at the end (Same logic of Creating a list)
 Deletion
Deletion operation is used to delete an item (or node) from the linked
list. A node may be deleted from the
 Deletion of front/first node
 Deletion of last node
 Deleting a particular node
 Traversing
Traversing is the process of going through all the nodes from one end
to another end of a linked list.
In a singly linked list we can visit from left to right (forward traversing) only.
But in doubly linked list forward and backward traversing is possible.
 Searching
Searching is the process of searching a particular element from the
list.
 Concatenation
Concatenation is the process of appending the second list to the end of
the first list.
Consider a list A having n nodes and list B with m nodes. Then the
concatenation operation will place the 1st node of B in the (n+1)th node of
list A.
After concatenation A will contain (n + m) nodes.

3.4 Implementing a Singly Linked List


In a singly linked list each node in the list stores the contents of the node and
a pointer or reference to the next node in the list. It does not store any pointer
or reference to the previous node. It is called a singly linked list because each
node only has a single link to another node. To store a single linked list, you
only need to store a reference or pointer to the first node in that list. The last
node has a pointer to nothingness to indicate that it is the last node.

ITSE205- Data Structures and Algorithms P a g e 48 | 115


Implementation of singly linked list

ITSE205- Data Structures and Algorithms P a g e 49 | 115


ITSE205- Data Structures and Algorithms P a g e 50 | 115
Inserting a node in beginning

ITSE205- Data Structures and Algorithms P a g e 51 | 115


Deleting first node from list

3.5 Linked Stack


Linked stack is an implementing stacks using linked List.
In linked stack push and pop are done only from one end.
This means push(insertion) will be carried at beginning and
pop(delete) will be carried at beginning.

It similar to
Insert at beginning
Delete from beginning of a linked list.

In push operation using linked list, it is not required to check


‘Overflow’ condition.
It is required to check ‘Underflow’ condition in Pop operation.
Typically, linked stack has the following operations:
Push – Insert node at beginning
Pop – Delete first node
Display – print all node values
Size – find number of nodes in linked stack.

Implementation of Linked Stack

#include <iostream>
using namespace std;
class Node
{
public:
int data;
Node *addr;
}*top;

ITSE205- Data Structures and Algorithms P a g e 52 | 115


class myClass
{
public:
myClass()
{
top = NULL;
}
void push(int p);
void pop();
void display();
};

void myClass ::pop()


{
if (top == NULL)
cout<<endl<<"stack underflow ";
else
{
cout<<endl<<"deleted node is "<<top->data;
top=top->addr;
}
}
void myClass :: push(int p)
{
Node *temp= new Node();
temp->data=p;
temp->addr=NULL;
if(top == NULL)
top = temp;
else
{
temp->addr = top;

ITSE205- Data Structures and Algorithms P a g e 53 | 115


top = temp;
}
}
void myClass ::display()
{
if (top == NULL)
cout<<endl<<"stack is empty ";
else
{
Node *t;
t=top;
cout<<endl<<" Stack elements are "<<endl;
while(t!= NULL)
{
cout<<t->data<< "->" ;
t=t->addr;
}
}
}

int main()
{
myClass *obj;
obj = new myClass();
top = NULL;
int ch, val;
do
{
cout<<endl<<"1. Push to stack ";
cout<<endl<<"2. Pop from stack ";
cout<<endl<<"3. Print stack elements ";
cout<<endl<<"4. exit";

ITSE205- Data Structures and Algorithms P a g e 54 | 115


cout<<endl<<"select choice ";
cin>>ch;
switch(ch)
{
case 1:
cout<<endl<<"enter value to push ";
cin>>val;
obj->push(val);
break;
case 2:
obj->pop();
break;
case 3:
obj->display();
break;
}
}while (ch!=4);
cout<<endl<<" linked stack tested ";
return 0;
}

3.6 Linked Queue:

ITSE205- Data Structures and Algorithms P a g e 55 | 115


Linked queue is an implementation of queues using linked list.
This is very similar to queue. Associated with queue/linked queue, there are
two pointers: front and rear.
Insertion will be done at rear end whereas deletion will be done at front end.
During enqueue operation, no ‘Overflow condition’ will be checked.
During dequeue operation, ‘under flow’ condition needs to be checked.
Typically, linked queue has the following operations:
Enqueue – insert element into queue
Dequeue – delete element into queue
Display – print all queue elements
Size – find number of nodes in the queue

Linked Queue Implementation


#include <iostream>
using namespace std;
class Node
{
public:
int data;
Node *addr;
}*f, *r;

class myClass
{
public:
myClass()
{
f = NULL;
r = NULL;
}
void insertQue(int p);
void delet();
void display();
};

void myClass :: display()


{
if (f==NULL)
cout<<endl<<"Queue is empty.....";
else
{
Node *temp = f;
cout<<endl<<" Queue values are " <<endl;
while(temp !=NULL)
{
cout<<temp->data << " -> ";
temp=temp->addr;
}

ITSE205- Data Structures and Algorithms P a g e 56 | 115


}
}

void myClass :: delet()


{
if (f==NULL)
cout<<endl<< " Queue Underflow ";
else
{
Node *temp = f;
int x = temp->data;
f=f->addr;
delete temp;
cout<<endl<<" deleted value is " << x;
}
}

void myClass :: insertQue(int t)


{
Node *temp = new Node();
temp->data = t;
temp ->addr = NULL;
if (f==NULL)
{
f = r = temp;
}
else
{
r -> addr = temp;
r = temp;
}
cout<<endl<<" Insert successful ";
}

int main()
{
myClass *ob;
ob = new myClass();

int ch, val;


do
{
cout<<endl<<" \t ***Queue operations*** ";
cout<<endl<<"\t 1. Insert a node ";
cout<<endl<<"\t 2. Delete node ";
cout<<endl<<"\t 3. Display Queue ";
cout<<endl<<"\t 4. Exit";
cout<<endl<<"\t select Your choice ";
cin>>ch;
switch (ch)
{
case 1:
cout<<endl<<"enter value to insert ";
cin>>val;

ITSE205- Data Structures and Algorithms P a g e 57 | 115


ob->insertQue(val);
break;
case 2:
ob->delet();
break;
case 3:
ob->display();
break;
}
}while(ch!=4);
cout<<endl<<"**Linked Queue Tested *** ";
return 0;
}

3.7 Circular linked list


In a circularly linked list, all nodes are linked in a continuous circle, without
using null. For lists with a front and a back (such as a queue), one stores a
reference to the last node in the list. The next node after the last node is the
first node.
In the circular linked list we can insert elements anywhere in the list whereas
in the array we cannot insert element anywhere in the list because it is in the
contiguous memory. In the circular linked list the previous element stores the
address of the next element and the last element stores the address of the
starting element. The elements points to each other in a circular way which
forms a circular chain. The circular linked list has a dynamic size which means
the memory can be allocated when it is required.

ITSE205- Data Structures and Algorithms P a g e 58 | 115


Applications of Circular Linked List
 The real life application where the circular linked list is used is our
Personal Computers, where multiple applications are running. All the
running applications are kept in a circular linked list and the OS gives a
fixed time slot to all for running. The Operating System keeps on
iterating over the linked list until all the applications are completed.
 Another example can be Multiplayer games. All the Players are kept in
a Circular Linked List and the pointer keeps on moving forward as a
player's chance ends.
 Circular Linked List can also be used to create Circular Queue. In a
Queue we have to keep two pointers, FRONT and REAR in memory all
the time, where as in Circular Linked List, only one pointer is required.

3.8 Doubly Linked List


 Doubly-linked list is a linked data structure that consists of a set of
sequentially linked records called nodes.
 Each node contains two fields, called links, that are references to the
previous and to the next node in the sequence of nodes.

In a single linked list, every node has link to its next node in the sequence.
So, we can traverse from one node to other node only in one direction and
we cannot traverse back. We can solve this kind of problem by using double
linked list.
Double linked list can be defined as follows...
Double linked list is a sequence of elements in which every element
has links to its previous element and next element in the sequence.

ITSE205- Data Structures and Algorithms P a g e 59 | 115


In double linked list, every node has link to its previous node and next node.
So, we can traverse forward by using next field and can traverse backward by
using previous field. Every node in a double linked list contains three fields.

Creation and display of a doubly linked list


#include <iostream>
using namespace std;
class Node
{
public:
int data;
Node *next;
Node *prev;
}*start, *temp1, *temp2;

class doubly
{
public :
void addend(int p);
void display();
};

void doubly :: addend(int p)


{
temp1=new Node();
temp1->data=p;

if(start==NULL)
{
start=temp1;
temp1->next=NULL;
temp1->prev=NULL;
}
else
{
temp2=start;
while(temp2->next!=NULL)
temp2=temp2->next;

temp2->next=temp1;
temp1->prev=temp2;
temp1->next=NULL;
}
cout<<endl<< "node added at end " ;
}

void doubly ::display()

ITSE205- Data Structures and Algorithms P a g e 60 | 115


{
temp1=start;
if(start==NULL)
cout<<"no nodes to display"<<endl;
else
{
cout<<endl<<" List values are " <<endl;
while(temp1->next!=NULL)
{
cout<<temp1->data<<"->";
temp1=temp1->next;
}
cout<<temp1->data<<endl;
}
}

int main()
{
start=NULL;
int ch, val;
doubly *d = new doubly();
do
{
cout<<endl<<"1. add node at end ";
cout<<endl<<"2. display node values ";
cout<<endl<<"3. exit ";
cout<<endl<<"select choice ";
cin>>ch;
switch(ch)
{
case 1:
cout<<endl<<"enter value to add ";
cin>>val;
d->addend(val);
break;
case 2:
d->display();
break;
}
}while(ch!=3);
cout<<endl<<"Doubly list tested ";
return 0;
}

ITSE205- Data Structures and Algorithms P a g e 61 | 115


Exercises:
1. Develop a program that performs the following on a singly linked list:
1. Insert a node at begin
2. Insert a node at end
3. Insert a node in middle
4. Search for a node
5. Print all node values
1. Develop a program that performs the following on a singly linked list:
1. Insert a node at begin
2. Insert a node at end
3. Insert a node in middle
4. Delete a node at beginning
5. Delete a node at end
6. Delete a node in middle
7. Find number of nodes in linked list
8. Search for a node
9. Print all node values
2. Develop a program that performs the following on a circular linked list:
1. Insert a node
2. Delete node
3. Print all node values

3. Differentiate between linked stack and linked queues with respect to


implementation
4. Develop a program to add two polynomials using linked list.

5. Develop a program to add two polynomials using linked list.

6. Develop a program to subtract two polynomials using linked list.

ITSE205- Data Structures and Algorithms P a g e 62 | 115


Chapter #4 Searching and Sorting Techniques
Objectives
At the end of this chapter students will be able to:
 Understand the concepts of sorting and searching
 Implement searching algorithms
 Implement sorting techniques
 Apply sorting and searching techniques for a given problem

4.0 Linear Search Algorithm


Linear search is a very simple search algorithm. In this type of search, a
sequential search is made over all items one by one. Every item is checked
and if a match is found then the position of that particular item is returned,
otherwise the search continues till the end of the data collection.
For example, consider an array of integers of size N. You should find and print
the position of all the elements with value X. Here, the linear search is based
on the idea of matching each element from the beginning of the list to the
end of the list with the integer X, and then printing the position of the element
if the condition is `True'.

#include <iostream>
using namespace std;
int linear(int *ar,int key, int siz)
{
for(int k=0; k<siz; k++)
if (ar[k]==key)
return (k+1);

return -1;
}
int main()
{
int *a,t,n,k,pos;

cout<<endl<<"Enter array size ";


cin>>n;
a= new int [n];
for(k=0; k<n; k++)
{
cout<<endl<<"enter array value " ;
cin>>a[k];
}
cout<<endl<<"Enter value to search ";
cin>>t;

ITSE205- Data Structures and Algorithms P a g e 63 | 115


pos = linear(a,t,n);
if(pos==-1)
cout<<endl<<"Element not found ";
else
cout<<endl<<"Element found at position " <<pos;

return 0;
}

4.1 Binary Search Algorithm


This search algorithm works on the principle of divide and conquer. For this
algorithm to work properly, the data collection should be in the sorted form.
Binary search looks for a particular item by comparing the middle most item
of the collection. If a match occurs, then the index of item is returned. If the
middle item is greater than the item, then the item is searched in the sub-
array to the left of the middle item. Otherwise, the item is searched for in the
sub-array to the right of the middle item. This process continues on the sub-
array as well until the size of the sub array reduces to zero.
#include <iostream>
using namespace std;
int binary(int *ar,int key, int siz)
{
int low=0, high=siz-1, mid;
mid = (low+high)/2;

while(low<=high)
{
if(ar[mid]==key)
return mid+1;
if(ar[mid]>key)
high = mid -1;
else
low = mid +1;

mid = (low+high)/2;
}
return -1;
}
int main()
{
int *a,t,n,k,pos;

cout<<endl<<"Enter array size ";


cin>>n;
a= new int [n];
for(k=0; k<n; k++)
{
cout<<endl<<"enter array value " ;

ITSE205- Data Structures and Algorithms P a g e 64 | 115


cin>>a[k];
}
cout<<endl<<"Enter value to search ";
cin>>t;
pos = binary(a,t,n);
if(pos==-1)
cout<<endl<<"Element not found ";
else
cout<<endl<<"Element found at "<<pos<<" position";

return 0;
}

4.2 Selection Sort algorithm


Selection Sort algorithm is used to arrange a list of elements in a particular
order (Ascending or Descending). In selection sort, the first element in the
list is selected and it is compared repeatedly with remaining all the elements
in the list. If any element is smaller than the selected element (for Ascending
order), then both are swapped. Then we select the element at second position
in the list and it is compared with remaining all elements in the list. If any
element is smaller than the selected element, then both are swapped. This
procedure is repeated till the entire list is sorted. If array has 5 elements,
selection sort takes maximum 4 iterations to sort the array.

ITSE205- Data Structures and Algorithms P a g e 65 | 115


Here …….
Array Size : 05
Number of maximum iterations: 04

#include <iostream>
using namespace std;

class selection
{
int siz, *ar;
public:
void getInput();
void selectSort();
void display();
};
void selection :: getInput()
{
cout<<endl<<"enter array size ";
cin>>siz;
ar = new int[siz];
for(int k=0; k<siz; k++)
{
cout<<endl<<"Enter array Value ";
cin>>ar[k] ;
}
}
void selection ::display()
{
for(int k=0; k<siz; k++)
cout<<endl<<ar[k];
}
void selection ::selectSort()
{

ITSE205- Data Structures and Algorithms P a g e 66 | 115


int k,m,t;

for(k=0; k<siz-1; k++)


{
for(m=k+1; m<siz; m++)
if(ar[k]>ar[m])
{
t = ar[k];
ar[k]=ar[m];
ar[m]=t;
}
}
}
int main()
{
selection *s = new selection();
s->getInput();
cout<<endl<<"Array values before sorting ";
s->display();
s->selectSort();
cout<<endl<<"Array values after sorting ";
s->display();
return 0;
}

4.3 Bubble Sort algorithm


Bubble sort is a simple sorting algorithm. This sorting algorithm is
comparison-based algorithm in which each pair of adjacent elements is
compared and the elements are swapped if they are not in order.
It is called Bubble sort, because with each iteration the largest element in the
list bubbles up towards the last place, just like a water bubble rises up to the
water surface. Sorting takes place by stepping through all the data items one-
by-one in pairs and comparing adjacent data items and swapping each pair
that is out of order.

ITSE205- Data Structures and Algorithms P a g e 67 | 115


#include <iostream>
using namespace std;

class Bubble
{
int siz, *ar;
public:
void getInput();
void BubbleSort();
void display();
};
void Bubble :: getInput()
{
ITSE205- Data Structures and Algorithms P a g e 68 | 115
cout<<endl<<"enter array size ";
cin>>siz;
ar = new int[siz];
for(int k=0; k<siz; k++)
{
cout<<endl<<"Enter array Value ";
cin>>ar[k] ;
}
}
void Bubble ::display()
{
for(int k=0; k<siz; k++)
cout<<endl<<ar[k];
}
void Bubble ::BubbleSort()
{
for(int k=0; k<siz-1; k++)
{
for(int m=0; m<siz-1-k; m++)
{
if (ar[m]>ar[m+1])
{
int temp=ar[m];
ar[m]=ar[m+1];
ar[m+1]=temp;
}
}
}
}
int main()
{
Bubble *s = new Bubble();
s->getInput();
cout<<endl<<"Array values before sorting ";
s->display();
s->BubbleSort();
cout<<endl<<"Array values after sorting ";
s->display();
return 0;
}

ITSE205- Data Structures and Algorithms P a g e 69 | 115


4.4 Insertion Sort algorithm

The insertion sort, unlike the other sorts, passes through the array only
once. The insertion sort is commonly compared to organizing a handful of
playing cards. You pick up the random cards one at a time. As you pick up
each card, you insert it into its correct position in your hand of organized
cards.
Imagine that you are playing a card game. You're holding the cards in your
hand, and these cards are sorted. The dealer hands you exactly one new card.
You have to put it into the correct place so that the cards you're holding are
still sorted. In selection sort, each element that you add to the sorted subarray
is no smaller than the elements already in the sorted subarray. But in our card
example, the new card could be smaller than some of the cards you're already
holding, and so you go down the line, comparing the new card against each
card in your hand, until you find the place to put it. You insert the new card
in the right place, and once again, your hand holds fully sorted cards. Then
the dealer gives you another card, and you repeat the same procedure. Then
another card, and another card, and so on, until the dealer stops giving you
cards.

Example:
Consider the following array a with 7 elements as
35 , 20 , 40 , 100 , 3 , 10 , 15

Since ar[1] < ar[0],


insert element ar[1] before ar[0] ,
giving the following array ,

Since ar[2]>ar[1] , no action is performed

Since ar[3]>ar[2] , again no action is performed.

ITSE205- Data Structures and Algorithms P a g e 70 | 115


Since ar[4] is less than ar[3] , ar[2] ,ar[1] as well as ar[0] , therefore in
insert ar[4] before ar[0] , giving the following array,

Since ar[5] is less than ar[4],ar[3],ar[2] and ar[1] , therefore insert ar[5]
before ar[1] , giving the following array.

Since ar[6] is less than ar[5],ar[4],ar[3], and ar[2] , therefore insert ar[6]
before ar[2] giving the following sorted array.

#include <iostream>
using namespace std;

class Insertion
{
int siz, *ar;
public:
void getInput();
void InsertSort();
void display();
};
void Insertion :: getInput()
{
cout<<endl<<"enter array size ";
cin>>siz;
ar = new int[siz];
for(int k=0; k<siz; k++)
{
cout<<endl<<"Enter array Value ";
cin>>ar[k] ;
}
}
void Insertion ::display()
{
for(int k=0; k<siz; k++)
cout<<endl<<ar[k];
}
ITSE205- Data Structures and Algorithms P a g e 71 | 115
void Insertion ::InsertSort()
{
int k,temp,j;
for(k=1;k<=siz;k++)
{
temp=ar[k]; j=k-1;
while((temp<ar[j])&&(j>=0))
{
ar[j+1]=ar[j];
j=j-1 ;
}
ar[j+1]=temp;
}
}

int main()
{
Insertion *s = new Insertion();
s->getInput();
cout<<endl<<"Array values before sorting ";
s->display();
s->InsertSort();
cout<<endl<<"Array values after sorting ";
s->display();
return 0;
}

ITSE205- Data Structures and Algorithms P a g e 72 | 115


4.5 Merging two sorted arrays into a single array

Consider the following example:

Array1 and Array2 are two sorted arrays. Now, let us merge these two arrays
into a single array.

Compare 14 and 6. Move 6 to Array3.

Compare 14 and 23. Move 14 to Array3

Compare 23 and 33. Move 23 to Array3

Compare 45 and 33. Move 33 to Array3

ITSE205- Data Structures and Algorithms P a g e 73 | 115


Compare 45 and 42. Move 42 to Array3

Compare 45 and 67. Move 45 to Array3

Compare 98 and 67. Move 67 to Array3

Array2 values are completes. Move remaining values of Array1 to Array3

ITSE205- Data Structures and Algorithms P a g e 74 | 115


4.6 Merge Sort using divide and conquer
 Merge sort divides up an unsorted list until the above condition is met and
then sorts the divided parts back together in pairs.
 Specifically, this can be done by recursively dividing the unsorted list in
half, merge sorting the right side then the left side and then merging the
right and left back together.
Given a list L with a length k:
 If k == 1  the list is sorted
 Else:
 Merge Sort the left side (1 thru k/2)
 Merge Sort the right side (k/2+1 thru k)
 Merge the right side with the left side

Input Array is divided into two parts.

Again divide

Again Divide

ITSE205- Data Structures and Algorithms P a g e 75 | 115


Again Divide

Now, Array is divided into all single elements.


Now, Merging will take place.

ITSE205- Data Structures and Algorithms P a g e 76 | 115


4.7 Quick Sort algorithm
Like merge sort, quicksort uses divide-and-conquer, and so it's a recursive
algorithm. The way that quicksort uses divide-and-conquer is a little different
from how merge sort does. In merge sort, the divide step does hardly
anything, and all the real work happens in the combine step. Quicksort is the
opposite: all the real work happens in the divide step. In fact, the combine
step in quicksort does absolutely nothing.
Here is how quicksort uses divide-and-conquer. As with merge sort, think of
sorting a subarray array[p..r], where initially the subarray
is array[0..n-1].
1. Divide by choosing any element in the subarray array[p...r]. Call this
element the pivot. Rearrange the elements in array[p...r] so that all
other elements in array[p...r] that are less than or equal to the pivot
are to its left and all elements in array[p...r] are to the pivot's right. We
call this procedure partitioning. At this point, it doesn't matter what
order the elements to the left of the pivot are in relative to each other,
and the same holds for the elements to the right of the pivot. We just
care that each element is somewhere on the correct side of the pivot.
As a matter of practice, we'll always choose the rightmost element in
the subarray, array[r], as the pivot. So, for example, if the subarray
consists of [9, 7, 5, 11, 12, 2, 14, 3, 10, 6], then we choose 6 as the
pivot. After partitioning, the subarray might look like [5, 2, 3, 6, 12, 7,
14, 9, 10, 11]. Let q be the index of where the pivot ends up.
ITSE205- Data Structures and Algorithms P a g e 77 | 115
2. Conquer by recursively sorting the subarrays array[p..q-1] (all
elements to the left of the pivot, which must be less than or equal to
the pivot) and array[q+1..r] (all elements to the right of the pivot,
which must be greater than the pivot).
3. Combine by doing nothing. Once the conquer step recursively sorts,
we are done. Why? All elements to the left of the pivot, in array[p..q-
1], are less than or equal to the pivot and are sorted, and all elements
to the right of the pivot, in array[q+1..r], are greater than the pivot and
are sorted. The elements in array[p..r] can't help but be sorted!
Think about our example. After recursively sorting the subarrays to the left
and right of the pivot, the subarray to the left of the pivot is [2, 3, 5], and the
subarray to the right of the pivot is [7, 9, 10, 11, 12, 14]. So the subarray
has [2, 3, 5], followed by 6, followed by [7, 9, 10, 11, 12, 14]. The subarray
is sorted.

The base cases are subarrays of fewer than two elements, just as in merge
sort. In merge sort, you never see a subarray with no elements, but you can
in quicksort, if the other elements in the subarray are all less than the pivot
or all greater than the pivot.
Let's go back to the conquer step and walk through the recursive sorting of
the subarrays. After the first partition, we have subarrays of [5, 2, 3] and [12,
7, 14, 9, 10, 11], with 6 as the pivot.
To sort the subarray [5, 2, 3], we choose 3 as the pivot. After partitioning,
we have [2, 3, 5]. The subarray [2], to the left of the pivot, is a base case
when we recurse, as is the subarray [5], to the right of the pivot.
To sort the subarray [12, 7, 14, 9, 10, 11], we choose 11 as the pivot,
resulting in [7, 9, 10] to the left of the pivot and [14, 12] to the right. After
these subarrays are sorted, we have [7, 9, 10], followed by 11, followed by
[12, 14].
Here is how the entire quicksort algorithm unfolds. Array locations in blue
have been pivots in previous recursive calls, and so the values in these
locations will not be examined or moved again:

ITSE205- Data Structures and Algorithms P a g e 78 | 115


ITSE205- Data Structures and Algorithms P a g e 79 | 115
4.8 Radix Sort algorithm
Radix sort is a non-comparative integer sorting algorithm that sorts data
with integer keys by grouping keys by the individual digits which share the
same significant position and value. Radix Sort puts the elements in order by
comparing the digits of the numbers.

Example:
Consider the following 9 numbers:
493 812 715 710 195 437 582 340 385
We should start sorting by comparing and ordering the one's digits:

Notice that the numbers were added onto the list in the order that they were
found, which is why the numbers appear to be unsorted in each of the sublists
above. Now, we gather the sublists (in order from the 0 sublist to the 9 sublist)
into the main list again:
340 710 812 582 493 715 195 385 437
Now, the sublists are created again, this time based on the ten's digit:

Now the sublists are gathered in order from 0 to 9:


710 812 715 437 340 582 385 493 195
Finally, the sublists are created according to the hundred's digit:

ITSE205- Data Structures and Algorithms P a g e 80 | 115


At last, the list is gathered up again:
195 340 385 437 493 582 710 715 812

4.9 Hashing
We develop different data structures to manage data in the most efficient
ways. Let’s look at the common data structures we have:
Arrays: Searching an array in the worst case is O(N). Cannot grow and shrink.
Linked Lists: This has also the search rime similar to an array O(N). can grow
and shrink. But searching is still the problem.
Binary search: This has a better search time of O(logN). The better compared
to above all.
We can improve the search time by using an approach called hashing. This
led us to the use of Hash Tables using hashing. In theory, insertion, deletion
and lookups can done in constant time that is O(1) using hashing.
Hashing is the process of indexing and retrieving element (data) in a data
structure to provide faster way of finding the element using the hash key.
Hash Table is basically an array which stores the data. But the data storing
process is done with the help of a function which is known as Hash Function.
Hashing is the process of mapping large amount of data item to a smaller
table with the help of a hashing function. The essence of hashing is to facilitate
the next level searching method when compared with the linear or binary
search. The advantage of this searching method is its efficiency to hand vast
amount of data items in a given collection (i.e. collection size).
In other words, hashing is a technique to convert a range of key values into
a range of indexes of an array. We're going to use modulo operator to get a
range of key values.
ITSE205- Data Structures and Algorithms P a g e 81 | 115
Hash Table
Hash Table is a data structure which stores data in an associative manner. In
a hash table, data is stored in an array format, where each data value has its
own unique index value. Access of data becomes very fast if we know the
index of the desired data.
Hash Table is the result of storing the hash data structure in a smaller table
which incorporates the hash function within itself. The Hash Function primarily
is responsible to map between the original data item and the smaller table
itself. Here the mapping takes place with the help of an output integer in a
consistent range produced when a given data item (any data type) is provided
for storage and this output integer range determines the location in the
smaller table for the data item. In terms of implementation, the hash table is
constructed with the help of an array and the indices of this array are
associated to the output integer range.
Following are basic primary operations of a hash table:
 Search − search an element in a hash table.
 Insert − insert an element in a hash table.
 Delete − delete an element from a hash table.

There are two types of Hash Tables:


Open-addressed Hash Tables and Separate-Chained Hash Tables.
 An Open-addressed Hash Table is a one-dimensional array indexed by
integer values that are computed by an index function called a hash
function.
 A Separate-Chained Hash Table is a one-dimensional array of linked
lists indexed by integer values that are computed by an index function
called a hash function.
ITSE205- Data Structures and Algorithms P a g e 82 | 115
•Hash tables are sometimes referred to as scatter tables
•Typical hash table operations are:
 Insertion.
 Initialization
 Searching
 Deletion.

There are two types of hashing:


1. Static hashing : In static hashing, the hash function maps search-key
values to a fixed set of locations.
2. Dynamic hashing : In dynamic hashing a hash table can grow to handle
more items. The associated hash function must change as the table grows.

The load factor of a hash table is the ratio of the number of keys in the table
to the size of the hash table.

Note:
 The higher the load factor, the slower the retrieval.
 With open addressing, the load factor cannot exceed 1.
 With chaining, the load factor often exceeds 1.
There are eight hashing methods they are:
 Direct method
 Subtraction method
 Modulo-division
 Midsquare
 Digit extraction
 Rotation
 Folding
 Pseudorandom generation

ITSE205- Data Structures and Algorithms P a g e 83 | 115


Direct Method
In direct hashing the key is the address without any algorithmic
manipulation.
 Direct hashing is limited, but it can be very powerful because it
guarantees that there are no synonyms and therefore no collision.

Modulo-division Method
 This is also known as division remainder method.
 This algorithm works with any list size, but a list size that is a prime
number produces fewer collisions than other list sizes.
 The formula to calculate the address is:
Address = key MODULO listsize + 1
Where listsize is the number of elements in the array.

Example:
Given data :
Keys are : 137456 214562 140145
137456 % 19 +1 = 11
214562 % 19 + 1 = 15
140145 % 19 + 1 = 2

ITSE205- Data Structures and Algorithms P a g e 84 | 115


Folding Method
The folding method for constructing hash functions begins by dividing the
item into equal-size pieces (the last piece may not be of equal size). These
pieces are then added together to give the resulting hash value. For example,
if our item was the phone number 436-555-4601, we would take the digits
and divide them into groups of 2 (43,65,55,46,01). After the
addition, 43+65+55+46+0143+65+55+46+01, we get 210. If we assume
our hash table has 11 slots, then we need to perform the extra step of dividing
by 11 and keeping the remainder.
In this case 210 % 11210 % 11 is 1, so the phone number 436-555-4601
hashes to slot 1. Some folding methods go one step further and reverse every
other piece before the addition.
For the above example, we
get 43+56+55+64+01=21943+56+55+64+01=219 which
gives 219 % 11=10219 % 11=10.

Midsquare Method
In midsquare hashing the key is squared and the address is selected from the
middle of the square number.
Another numerical technique for constructing a hash function is called
the mid-square method. We first square the item, and then extract some
portion of the resulting digits. For example, if the item were 44, we would first
compute 442=1,936. By extracting the middle two digits, 93, and performing
the remainder step, we get 5 (93 % 11). Below table shows items under both
the remainder method and the mid-square method. You should verify that
you understand how these values were computed.

ITSE205- Data Structures and Algorithms P a g e 85 | 115


Pseudorandom Hashing
A common random-number generator is shown below.
y= ax + c
To use the pseudorandom-number generator as a hashing method, we set x
to the key, multiply it by the coefficient a, and then add the constant c. The
result is then divided by the list size, with the remainder being the hashed
address.
Example:
Y= ((17 * 121267) + 7) modulo 307
Y= (2061539 + 7) modulo 307
Y= 2061546
Y=41

Collision Resolution
When two items hash to the same slot, we must have a systematic method
for placing the second item in the hash table. This process is called collision
resolution.
One method for resolving collisions looks into the hash table and tries to find
another open slot to hold the item that caused the collision. A simple way to
do this is to start at the original hash value position and then move in a
sequential manner through the slots until we encounter the first slot that is
empty. Note that we may need to go back to the first slot (circularly) to cover
the entire hash table. This collision resolution process is referred to as open
addressing in that it tries to find the next open slot or address in the hash
table. By systematically visiting each slot one at a time, we are performing an
open addressing technique called linear probing

ITSE205- Data Structures and Algorithms P a g e 86 | 115


Exercises:

1. Implement Merge sort algorithm

2. Implement Radix sort algorithm

3. Implement Quick sort algorithm

4. Implement selection sort that sorts both characters and integers based on user choice.

5. Implement linear search technique that searches for both characters and integers

bases on user choice.

6. Implement a linear search technique on a singly linked list

ITSE205- Data Structures and Algorithms P a g e 87 | 115


Chapter #5 Trees
Objectives
At the end of this chapter students will be able to:
 Understand the concepts trees and terminologies
 Understand binary trees
 Apply tree traversal techniques
 Construct binary and binary search trees
 Understand heap sort technique

5.0 Introduction
A tree is a data structure for representing hierarchical data.
A tree can be defined as a finite set of one or more data items (or nodes)
such that:
1). There is a special node called the root of the tree.
2). Removing nodes (or data item) are partitioned into number of mutually
exclusive (i.e., disjoined) subsets each of which is itself a tree, are called
sub tree.

A tree is a finite set of one or more nodes such that:


 There is a specially designated node called the root.
 The remaining nodes are partitioned into n>=0 disjoint sets T1, ...,
Tn, where each of these sets is a tree.
 We call T1, ..., Tn the subtrees of the root.
Tree store elements hierarchically
 The top element: root
 Except the root, each element has a parent
 Each element has 0 or more children
 Root is a specially designed node (or data items) in a
tree
 Degree of a node is the number of sub trees of a node in a given tree
 The degree of a tree is the maximum degree of node in a given tree.
 A node with zero degree is called a terminal node or leaf.
ITSE205- Data Structures and Algorithms P a g e 88 | 115
 Any node whose degree is not zero is called non-terminal node.
 Depth of a tree is the maximum level of any node in a given tree.
The degree of A is 3;
The degree of C is 1.
The degree of B is _____
The degree of the given tree is 3.
The leaf or terminal nodes are in the tree
is ___.
The non-terminal nodes are in the tree is
_____
Children of the same parent are siblings.
EF are Siblings of B, HIJ are siblings
of D
The height of a node is the length of the longest downward path to a leaf
from that node.
The height of the root is the height of the tree.
The depth of a node is the length of the path to its root (i.e., its root path).

Height of the Tree: 4


(Length of Longest path from root)

The depth of a node is the length of


the path from the root to that node;
the depth of J is 4
the depth of D is 3
the depth of A is 1
The depth of E is _____

ITSE205- Data Structures and Algorithms P a g e 89 | 115


Exercise:

 Number of nodes ______


 Height ______
 Root Node _______
 Number of Leaf nodes ________
 Number of Non terminal nodes _____
 Number of levels ________
 Siblings of E _________
 Depth of F ___________

Representation of Tree
A
List Representation
• ( A ( B ( E ( K, L ), F ), C ( G ), D ( H ( M ), I, J ) ) )
• The root comes first, followed by a list of sub-trees B C D

E F G H I J

K L M
5.1 Why Tree?
Unlike Array and Linked List, which are linear data structures, tree is
hierarchical (or non-linear) data structure.
1) One reason to use trees might be because you want to store information
that naturally forms a hierarchy. For example, the file system on a computer:
2) If we organize keys in form of a tree (with some ordering e.g., BST), we
can search for a given key in moderate time (quicker than Linked List and
slower than arrays). Self-balancing search trees like AVL and Red-Black
trees guarantee an upper bound of O(Log n) for search.
3) We can insert/delete keys in moderate time (quicker than Arrays and
slower than Unordered Linked Lists). Self-balancing search
trees like AVL and Red-Black trees guarantee an upper bound of O(Log n) for
insertion/deletion.
4) Like Linked Lists and unlike Arrays, Pointer implementation of trees don’t
have an upper limit on number of nodes as nodes are linked using pointers.

ITSE205- Data Structures and Algorithms P a g e 90 | 115


Following are the common uses of tree.
1. Manipulate hierarchical data.
2. Make information easy to search (see tree traversal).
3. Manipulate sorted lists of data.
4. As a workflow for compositing digital images for visual effects.

5.2 Binary Trees


Definition:
A binary tree is a tree in which no node can have more than two children.
Or
A binary tree is a tree such that
 every node has at most 2 children
 each node is labeled as being either a left child or a right child. Or
A special class of trees: max degree for each node is 2

Recursive definition:
• a binary tree is empty;
• or it consists of
• a node (the root) that stores an element
• a binary tree, called the left sub tree of T(T1)
• a binary tree, called the right sub tree of T (T2)

Examples:

ITSE205- Data Structures and Algorithms P a g e 91 | 115


Strictly Binary Tree
The tree is said to be strictly binary tree, if every non-leaf node in a binary
tree has non-empty left and right sub trees. A strictly binary tree with n leaves
always contains 2n – 1 nodes.

A complete binary tree is one where all the levels are full with exception to
the last level and it is filled from left to right.

binary tree Vs ordinary tree


 A binary tree can be empty where as a tree cannot.
 Each element in binary tree has exactly two sub trees (one or both of
these sub trees may be empty). Each element in a tree can have any
number of sub trees.
 The sub tree of each element in a binary tree are ordered, left and right
sub trees. The sub trees in a tree are unordered.

If a binary tree has only left sub trees,


then it is called left skewed binary
tree.
If a binary tree has only right sub
trees, then it is called right skewed
binary tree.

ITSE205- Data Structures and Algorithms P a g e 92 | 115


Binary Tree Representation
1. Sequential representation using arrays
2. Linked list representation
Example: Binary Tree of Depth 3

5.3 Binary Tree Traversal


 Preorder (Traverse root, left, right)
 Inorder (Traverse Left, root, right)
 Postorder (Traverse Left, right, root)
With preorder traversal we first visit a root node, then traverse its left
subtree, and then traverse its right subtree.
With inorder traversal we first traverse the left subtree, then visit the root
node, and then traverse its right subtree.
With postorder traversal we first traverse the left subtree, then traverse
the right subtree, and finally visit the root node
Preorder Traversal
// preorder traversal algorithm
preorder(TreeNode<T> n)
{
if (n != null)
{
visit(n)
preorder (n.getLeft());
preorder (n.getRight());
}
}
ITSE205- Data Structures and Algorithms P a g e 93 | 115
Inorder Traversal
With inorder traversal we first traverse the left subtree, then visit the root
node, and then traverse its right subtree.

// InOrder traversal algorithm


inOrder(TreeNode<T> n)
{
if (n != null)
{
inOrder(n.getLeft());
visit(n)
inOrder(n.getRight());
}
}

ITSE205- Data Structures and Algorithms P a g e 94 | 115


Postorder
With postorder traversal we first traverse the left subtree, then traverse the
right subtree, and finally visit the node.

// postorder traversal algorithm


postorder(TreeNode<T> n)
{
if (n != null)
{
postorder(n.getLeft());
postorder(n.getRight());
visit(n)
}
}

ITSE205- Data Structures and Algorithms P a g e 95 | 115


5.4 Binary Search Tree
A binary search tree is a binary tree with a special property called the BST-
property, which is given as follows:
 For all nodes x and y, if y belongs to the left subtree of x, then the
key at y is less than the key at x, and if y belongs to the right subtree
of x, then the key at y is greater than the key at x.

Binary Search Tree Property:


The value stored at a node is greater than the value
stored at its left child and less than the value stored
at its right child.
In a BST, the value stored at the root of a subtree
is greater than any value in its left subtree and less
than any value in its right subtree!
In a BST, left most node is the smallest element
and right most node is the largest element.

Search a binary search tree


1. Start at the root
2. Compare the value of the item you are searching for with the value stored
at the root
3. If the values are equal, then item found; otherwise, if it is a leaf node,
then not found
4. If it is less than the value stored at the root, then search the left subtree
5. If it is greater than the value stored at the root, then search the right
subtree
6. Repeat steps 2-6 for the root of the subtree chosen in the previous step 4
or 5

ITSE205- Data Structures and Algorithms P a g e 96 | 115


Example: Consider the following Binary search tree. Search for 12

Searching Process:

Example:
Search for a node 45, from the below binary search tree.

1. Start at the root (25) node. 45 is greater than 25, search in the right
subtree.
2. 45 is less than 50, search in 50’s left subtree.
3. 45 is greater than 35, search in 35’s right subtree.
4. 45 is greater than 44, but 44 has no right subtree, so 45 is not in BST.
ITSE205- Data Structures and Algorithms P a g e 97 | 115
Deleting a node in a BST
We need to consider three different cases:
(1) Deleting a leaf node
(2) Deleting a node with only one child
(3) Deleting a node with two children

Case 1: Deleting a leaf node


We use the following steps to delete a leaf node from BST...
Step 1: Find the node to be deleted using search operation
Step 2: Delete the node using delete function (If it is a leaf) and terminate
the function.

Case 2: Deleting a node with one child


We use the following steps to delete a node with one child from BST...
Step 1: Find the node to be deleted using search operation
Step 2: If it has only one child, then create a link between its parent and
child nodes.
Step 3: Delete the node using delete function and terminate the function.

ITSE205- Data Structures and Algorithms P a g e 98 | 115


Case 3: Deleting a node with two children
We use the following steps to delete a node with two children from BST...
Step 1: Find the node to be deleted using search operation
Step 2: If it has two children, then find the largest node in its left subtree
(OR) the smallest node in its right subtree.
Step 3: Swap both deleting node and node which found in above step.
Step 4: Then, check whether deleting node came to case 1 or case 2 else
goto steps 2
Step 5: If it comes to case 1, then delete using case 1 logic.
Step 6: If it comes to case 2, then delete using case 2 logic.
Step 7: Repeat the same process until node is deleted from the tree.

ITSE205- Data Structures and Algorithms P a g e 99 | 115


Insertion Operation in BST
Step 1: Create a newNode with given value and set its left and right to NULL.
Step 2: Check whether tree is Empty.
Step 3: If the tree is Empty, then set root to newNode.
Step 4: If the tree is Not Empty, then check whether value of newNode is
smaller or larger than the node (here it is root node).
Step 5: If newNode is smaller than or equal to the node, then move to its left
child. If newNode is larger than the node, then move to its right child.
Step 6: Repeat the above step until we reach to a leaf node (i.e, reach to
NULL).
Step 7: After reaching a leaf node, then insert the newNode as left child if
newNode is smaller or equal to that leaf else insert it as right child.

Inorder traversal in a BST

ITSE205- Data Structures and Algorithms P a g e 100 | 115


5.5 Heap Sort
Heap sort algorithm is divided into two basic parts:
 Creating a Heap of the unsorted list.
 Then a sorted array is created by repeatedly removing the
largest/smallest element from the heap, and inserting it into the array.
The heap is reconstructed after each removal.
Heap is a special tree-based data structure, that satisfies the following
special heap properties:
1. Shape Property: Heap data structure is always a Complete Binary Tree,
which means all levels of the tree are fully filled.

2. Heap Property: All nodes are either [greater than or equal to] or [less
than or equal to] each of its children. If the parent nodes are greater than
their children, heap is called a Max-Heap, and if the parent nodes are smaller
than their child nodes, heap is called Min-Heap.

In min-heap, first element is the smallest. So, when we want to sort a list in
ascending order, we create a min-heap from that list, and picks the first
element, as it is the smallest, then we repeat the process with remaining
elements.
In max-heap, the first element is the largest, hence it is used when we need
to sort a list in descending order.

ITSE205- Data Structures and Algorithms P a g e 101 | 115


Initially on receiving an unsorted list, the first step in heap sort is to create a
Heap data structure (Max-Heap or Min-Heap). Once heap is built, the first
element of the Heap is either largest or smallest (depending upon Max-Heap
or Min-Heap), so we put the first element of the heap in our array. Then we
again make heap using the remaining elements, to again pick the first element
of the heap and put it into the array. We keep on doing the same repeatedly
until we have the complete sorted list in our array.

Exercises:
1. Construct all possible binary trees with 4 nodes
2. Write preorder, inorder and postorder values for the following expression
trees.

3. Give examples to insert a node in a BST.


4. Give examples to implement heap sort technique

ITSE205- Data Structures and Algorithms P a g e 102 | 115


Chapter#6 Graphs
Objectives
At the end of this chapter students will be able to:
 Understand the concepts graphs and terminologies
 Differentiate between graphs and trees
 Apply graph traversal techniques

6. 0 Introduction
Graphs are one of the unifying themes of computer science.
A graph G = (V;E) is defined by a set of vertices V , and a set of
edges E consisting of ordered or unordered pairs of vertices from V.

Graph is a data structure that consists of following two components:


1.A finite set of vertices also called as nodes.
2. A finite set of ordered pair of the form (u, v) called as edge.

The pair is ordered because (u, v) is not same as (v, u) in case of directed
graph(di-graph). The pair of form (u, v) indicates that there is an edge from
vertex u to vertex v. The edges may contain weight/value/cost.

Graphs are used to represent many real life applications: Graphs are used to
represent networks. The networks may include paths in a city or telephone
network or circuit network. Graphs are also used in social networks like
LinkedIn, Facebook. For example, in Facebook, each person is represented
with a vertex (or node).

Graphs can be used to model social structures based on different kinds of


relationships between people or groups.

ITSE205- Data Structures and Algorithms P a g e 103 | 115


Following is an example undirected graph with 5 vertices.

Applications

 Electronic applications.
 networks (roads, flights, communications)
 scheduling (project planning)
 Connecting with friends on social media, where each user is a vertex,
and when users connect they create an edge.
 Using GPS/Google Maps/Yahoo Maps, to find a route based on shortest
route.
 Google, to search for webpages, where pages on the internet are linked
to each other by hyperlinks; each page is a vertex and the link between
two pages is an edge.
 Graph theoretical concepts are widely used in Operations Research.

6.1 Graph representation


Following two are the most commonly used representations of graph.
1. Adjacency Matrix
2. Adjacency List
There are other representations also like, Incidence Matrix and Incidence
List. The choice of the graph representation is situation specific. It totally
depends on the type of operations to be performed and ease of use.

Adjacency Matrix:
Adjacency Matrix is a 2D array of size V x V where V is the number of
vertices in a graph.
Let the 2D array be adj[][], a slot adj[i][j] = 1 indicates that there is an edge
from vertex i to vertex j. Adjacency matrix for undirected graph is always
symmetric. Adjacency Matrix is also used to represent weighted graphs. If
adj[i][j] = w, then there is an edge from vertex i to vertex j with weight w.

ITSE205- Data Structures and Algorithms P a g e 104 | 115


The adjacency matrix for the above example graph is:

Adjacency List:

An array of linked lists is used. Size of the array is equal to number of vertices.
Let the array be array[]. An entry array[i] represents the linked list of vertices

adjacent to the ith vertex. This representation can also be used to represent

a weighted graph. The weights of edges can be stored in nodes of linked lists.
Following is adjacency list representation of the above graph.

6.2 Directed and Undirected Graphs

In directed graph, edges have specific direction. In Undirected graph, edges


are two-way.

Example:

G = (V, E) with vertex set


V = {0,1,2,3,4,5,6}
ITSE205- Data Structures and Algorithms P a g e 105 | 115
and edge set E =
{(0,2),(0,4),(0,5),(1,0),(2,1),(2,5),(3,1),(3,6),(4,0),(4,5),(6,3),(6,5)}

6.3 Graph Traversals


A traversal is a systematic procedure for exploring a graph by examining all
of its vertices and edges. Some algorithms require that every vertex of a
graph be visited exactly once. The order in which the vertices are visited may
be important, and may depend upon the particular algorithm.
The two common graph traversals: depth-first and breadth-first.

6.3.1 Depth-First Search


The DFS algorithm is a recursive algorithm that uses the idea of backtracking.
It involves exhaustive searches of all the nodes by going ahead, if possible,
else by backtracking.
Here, the word backtrack means that when you are moving forward and there
are no more nodes along the current path, you move backwards on the same
path to find nodes to traverse. All the nodes will be visited on the current path
till all the unvisited nodes have been traversed after which the next path will
be selected.
This recursive nature of DFS can be implemented using stacks.
The basic idea is as follows:
Pick a starting node and push all its adjacent nodes into a stack.
Pop a node from stack to select the next node to visit and push all its
adjacent nodes into a stack.
Repeat this process until the stack is empty.
However, ensure that the nodes that are visited are marked. This will
prevent you from visiting the same node more than once. If you do not

ITSE205- Data Structures and Algorithms P a g e 106 | 115


mark the nodes that are visited and you visit the same node more than
once, you may end up in an infinite loop.

Applications of Depth First Search


 For an unweighted graph, DFS traversal of the graph produces the
minimum spanning tree and all pair shortest path tree.
 Detecting cycle in a graph
A graph has cycle if and only if we see a back edge during DFS. So we
can run DFS for the graph and check for back edges.
 Path Finding
We can specialize the DFS algorithm to find a path between two given
vertices u and z.
i) Call DFS(G, u) with u as the start vertex.
ii) Use a stack S to keep track of the path between the start vertex and
the current vertex.
iii) As soon as destination vertex z is encountered, return the path as the
contents of the stack
 Topological Sorting:
Tropical sorting is mainly used for scheduling jobs from the given
dependencies among jobs. In computer science, applications of this type
arise in instruction scheduling, ordering of formula cell evaluation when re
computing formula values in spreadsheets, logic synthesis, determining
the order of compilation tasks to perform in make files, data serialization,
and resolving symbol dependencies in linkers.
 Solving puzzles with only one solution, such as mazes. (DFS can be
adapted to find all solutions to a maze by only including nodes on the
current path in the visited set.)

depth-first-search
1. mark vertex as visited
2. for each adjacent vertex
3. if unvisited
1. do a depth-first search on adjacent vertex

dfs(G, u):
while u has an unvisited neighbour in G
v := an unvisited neighbour of u

ITSE205- Data Structures and Algorithms P a g e 107 | 115


mark v visited
dfs(G, v)

Example:

ITSE205- Data Structures and Algorithms P a g e 108 | 115


Example: DFS in a directed graph
Conduct a depth-first search of the graph starting with node D

ITSE205- Data Structures and Algorithms P a g e 109 | 115


ITSE205- Data Structures and Algorithms P a g e 110 | 115
ITSE205- Data Structures and Algorithms P a g e 111 | 115
6.3.2 Breadth-First Search
Breadth First Search (BFS) algorithm traverses a graph in a breadth ward
motion and uses a queue to remember to get the next vertex to start a
search, when a dead end occurs in any iteration.
The simple description of the algorithm for a BFS is this:
1. Start at some node ‘i’. This is now our current node.
2. State that our current node is ‘visited’.
3. Now look at all nodes adjacent to our current node.
4. If we see an adjacent node that has not been ‘visited’, add it to the
queue.
5. Then pull out the first node on from the queue and traverse to it.
6. And go back to step 1.

Algorithm:
BFS(G,V)
Let Q be a Queue
Q.enqueue(V)
Label V as discovered
while Q is not empty
v <- Q.dequeue()
for all edges from v to w in G.adjacentEdges(v) do
if w is not labeled as discovered
Q.enqueue(w)
label w as discovered.

Applications
 Shortest Path, the shortest path is the path with least number of edges.
With Breadth First, we always reach a vertex from given source using
minimum number of edges. Also, in case of unweighted graphs, any
spanning tree is Minimum Spanning Tree and we can use either Depth or
Breadth first traversal for finding a spanning tree.
 Peer to Peer Networks. In Peer to Peer Networks like Bit Torrent, Breadth
First Search is used to find all neighbour nodes.
 Crawlers in Search Engines: Crawlers build index using Breadth First. The
idea is to start from source page and follow all links from source and keep
doing same. Depth First Traversal can also be used for crawlers, but the
advantage with Breadth First Traversal is, depth or levels of built tree can
be limited.

ITSE205- Data Structures and Algorithms P a g e 112 | 115


 Social Networking Websites: In social networks, we can find people within
a given distance ‘k’ from a person using Breadth First Search till ‘k’ levels.
 GPS Navigation systems: Breadth First Search is used to find all
neighbouring locations.
 Broadcasting in Network: In networks, a broadcasted packet follows
Breadth First Search to reach all nodes.
 Cycle detection in undirected graph: In undirected graphs, either Breadth
First Search or Depth First Search can be used to detect cycle. In directed
graph, only depth first search can be used.
 Ford–Fulkerson algorithm In Ford-Fulkerson algorithm, we can either use
Breadth First or Depth First Traversal to find the maximum flow. Breadth
First Traversal is preferred as it reduces worst case time complexity to
O(VE2).
 Path finding, we can either use Breadth First or Depth First Traversal to
find if there is a path between two vertices.
 Finding all nodes within one connected component: We can either use
Breadth First or Depth First Traversal to find all nodes reachable from a
given node.

ITSE205- Data Structures and Algorithms P a g e 113 | 115


Example: in a undirected graph

Example: BFS in a directed graph


Conduct a Breadth-first search of the graph starting with node D

ITSE205- Data Structures and Algorithms P a g e 114 | 115


Exercises:

1. Develop an application that implements DFS


2. Develop an application that implements BFS
3. Differentiate between BFS and DFS

ITSE205- Data Structures and Algorithms P a g e 115 | 115

S-ar putea să vă placă și