Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                
0% found this document useful (0 votes)
43 views

Handout Data Structures Final

The document discusses introduction to data structures and algorithms. It defines data structures as arrangements of data in computer memory that specify how data is stored, what operations can be performed on it and algorithms for those operations. It describes different ways data can be stored linearly or non-linearly using common data structures like linked lists, queues, stacks and trees. It also defines data types, abstract data types and algorithms.

Uploaded by

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

Handout Data Structures Final

The document discusses introduction to data structures and algorithms. It defines data structures as arrangements of data in computer memory that specify how data is stored, what operations can be performed on it and algorithms for those operations. It describes different ways data can be stored linearly or non-linearly using common data structures like linked lists, queues, stacks and trees. It also defines data types, abstract data types and algorithms.

Uploaded by

eyobeshete01
Copyright
© © All Rights Reserved
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 73

Data Structures & Algorithm – Lecture Notes

CHAPTER 1 – INTRODUCTION TO DATA STRUCTURES

Introduction to Data Structures

What is computer?
 Is a device that performs tasks based on given set of step-by-step instructions
(procedures)
 These set of procedures (step-by-step instructions) are called algorithms.
 The primary purpose of computer is not to perform calculations, but to store and retrieve
data as fast as possible.

How data is stored in computers?


The whole memory of a computer is partitioned in to small rooms on to which addresses are assigned
to each of them. So, when a computer stores data in its memory, it inserts the data by searching free
rooms. Thus, in retrieving &searching data costs time and there is inefficient use of memory. To be
saved from such costs, a study of how data is stored in memory and how to retrieve it is needed.

What is data structure?


Data structure is an arrangement of data in computer memory. It is an agreement about:

 How to store a collection of objects in memory


 What operations we can perform on that data
 The algorithms for those operations, and
 How time and apace efficient those algorithms are.

Generally, data in a computer can be stored in two ways, linearly or non-linearly.

Lnked list

Linear Queue

Data Stack ...


Structure
Tree
Non-linear
Graph ...

Page 1
Data Structures & Algorithm – Lecture Notes

When selecting one data structure to solve a problem, we should follow the following steps:

1. Analyze the problem to determine the basic operations that must be supported
2. Quantify the resource constraints for each operation
3. Select the data structure that best meets these requirements

Examples of applications of data structures:

 How does Google quickly find pages that contain search term?
 What is the fastest way to broadcast a message to a network of computers?
 How does your operating system track which memory (disk or RAM) is free?

What is Data type?


All kinds of data that can be stored in computer do not take the same space and the same operations
cannot be made on them. Example let’s take the name “Jhon” and the number “20”. The two items
(data) do not take the same memory space and also operations. Multiplication operation can be made
on the number item but not on the string item.

So, to handle these issues, a mechanism is devised known as “data type” system.

Data type means “item type to be stored in memory”. They are used to identify what amount of
memory size that should be assigned to a given data/item and what kinds of operations that could be
made on the item.

Based on c++ programming language, data types are categorized as primitive/built-in and
derived/user-defined.

integer

Numeric
floating
point(double)
Primitive/ built-
in /Intrinsic
character and
string
Non-numeric

Data types boolean


classes and
structure

Derived/ user-
defined /non- interface
primitive

Array

Page 2
Data Structures & Algorithm – Lecture Notes

Examples:-

a. Built in data types: int, char, float, double, bool…


b. User-defined data types: structure, class

struct Time{
int hour;
int minute;
int second;
};

struct Box {
double length;
double breadth;
double height;
};

class Box {
public:
double length;
double breadth;
double height;

// Member functions declaration


double getVolume(void);
void setLength( double len );
void setBreadth( double bre );
void setHeight( double hei );
};

Page 3
Data Structures & Algorithm – Lecture Notes

Note:
 The difference between structures and class data types is classes contain data members and
the operations that control the data
 The user defined data types help us to create “Abstract Data types”.

What is abstract data type (ADT)?


Abstract data type is the logical picture/view of an item/set of items or data that is going to be
stored in a computer memory. Example: if we want to store basic information of books. Book is the
item to be stored, its characteristics we need may include book id, book name, author, date of
publishing… and the operations that may be done on them could be adding a new book to our list of
books, deleting a book …

So, by our logical view, a book is as what we have defined above, it is one kind of model/abstract
view created by us. This process of modeling/constructing a logical view or picture of an item is
called Data Abstraction.

To transform this abstract view/logical view in to computer program, we use the concept of user
defined data types like class data type.

Therefore, ADT is defined a set of objects together with a set of operations or an ADT is an
externally defined data type that holds some kind of data with some other operations that operate on
the data. Users of an ADT do not need to have any detailed information about the internal
representation of the data storage or implementation of the operations. It is called abstract because it
doesn’t specify how the operations of the ADT are going to be implemented.

Generally, in data abstraction, the model or logical view/picture of an object/item in the real world is
created in order to store information about it without specifying the implementation details.

Real world
object/item to be
modeled

ADT
/ Logical view of the
real world object

Page 4
Data Structures & Algorithm – Lecture Notes

ALGORITHM AND ALGORITHM ANALYSIS

Algorithm

The way data are organized in a computer’s memory is said to be Data Structure and
sequence of computational steps to solve a problem is said to be an Algorithm, in other
words, an Algorithm is a clearly specified set of simple instructions to be followed to
solve a problem. Therefore, a program/software is nothing but data structures plus
algorithms.

Properties of an algorithm

Finiteness: Algorithm must complete after a finite number of steps.


Definiteness: Each step must be clearly defined, having one and only one interpretation. At
each point in computation, one should be able to tell exactly what happens next.
Feasibility: It must be possible to perform each instruction using resources at hand.
Correctness: It must compute correct answer for all possible legal inputs.
Completeness: It must solve the problem completely.
Input/output: There must be a specified number of input values, and one or more result
values.
Other properties:

 Sequence:
Each step must have a unique defined preceding and succeeding step. The first step
(start step) and last step (halt step) must be clearly noted.
 Language Independence:
It must not depend on any one programming language.
 Effectiveness:
It must be possible to perform each step exactly and in a finite amount of time.
 Efficiency:
It must solve with the least amount of computational resources such as time and
space.
 Generality:
Algorithm should be valid on all possible inputs.

Example: Finding the minimum value the given integers.

int main(){
int a[] = { 23, 45, 71, 12, 87, 66, 20, 33, 15, 69 };
int min = a[0];
for (int i = 1; i < 10; i++) {
if (a[i] < min) min = a[i]; }
cout<<"The minimum value is: "<<min;

Page 5
Data Structures & Algorithm – Lecture Notes

return 0;
}

Algorithm Analysis
Algorithm analysis is the process of determining the amount of computing time and storage space
required by different algorithms, called complexity of algorithms. It is a study of computer program
performance and resource usage.

It is the process of establishing the function T(n) or S(n) for the given algorithm.

It is studies on computer resources that include:

 Running Time
 Memory Usage
 Communication Bandwidth

In general, it answers the question:

“what is the performance and category of a given algorithm during the three conditions (best-
case, average-case, &worst-case) in terms of the notations (O,Ω,Ɵ,o,ɷ) using the formal and
informal method of counting operations. ”

Complexity of Algorithm
Complexity of an algorithm is the function T(n) or S(n) describing the efficiency of an algorithm in
terms of the amount of data the algorithm must process.

It is represented by T(n) or S(n).

 T(n) – is a function describing the complexity (amount of time an algorithm takes) in-terms
of the amount of inputs “n” to the algorithm. It means that “the algorithm ‘A’ takes ‘T’
amount of time for the given set of ‘n’ inputs”.
 S(n) – is a function describing the complexity (amount of memory space an algorithm takes)
in-terms of the amount of inputs “n” to the algorithm. It means that “the algorithm ‘A’
takes ‘S’ amount of memory space for the given set of ‘n’ inputs”.

Note: the most common measure of complexity of algorithms is time complexity, T(n). And the
number of inputs give us the three testing conditions,
 If we test it in small number of inputs “n”, it is called best-case.
 If we test it in normal number of inputs “n”, it is called average-case.
 If we test it in very large number of inputs “n”, it is called worst-case.

The complexity function T(n) of a given algorithm is determined by counting the number of
operations of the algorithm.

Order of an algorithm based on its complexity


Order of an algorithm is the category of a given complexity function of an algorithm in terms of its
resource usage. Which means it is the process of determining the category of the established function
T(n) or S(n).

Page 6
Data Structures & Algorithm – Lecture Notes

It determines the rate at which the storage or time grows as a function of the size n, number of inputs.
Eg. O(n), O(n), O(n2), O(2n), O(logn), O(nlogn), O(n!) …

During algorithm analysis, 1st we drive the function T(n) for the algorithm, and then we determine the
order/category of T(n).

Order of a given algorithm or a function T(n) is written as O(T(n)). So, O(T(n)) – means:
“category/order of the function T(n).”
Example: if the time complexity function T(n) is categorized in logarithmic time, it is written as
O(T(n))= logn which is read as ”order/category of T(n) is equal to logn.”

Note: in calculating order of T(n),


 Constant coefficients, bases of logarithms, powers of logarithms … are omitted.
 From a polynomial, the highest degree is taken.
o Eg. if T(n)=2n3+4n-4 , it is said to be order of n3
o Eg. if T(n)=8log5n2 , it is said to be order of logn

Rules for computing complexity


1. The complexity function T(n) of a given algorithm is determined by counting the number of
operations of the algorithm. But there are two kinds of counting the operations:
 In-formal method: tries to count all the operations of the algorithm
 Formal method: counts operations by ignoring the variable initializations, loop
controls…

2. Selection statements: Running time of a selection statement (if, switch) is:


 { the time for the condition evaluation } + { the maximum of the running times for the
individual clauses in the selection }

3. Loops: Running time for a loop is equal to the running time for the statements inside
the loop multiplied by number of iteration. The total running time of nested loops, the
running time is multiplied by the product of the sizes of all the loops.
4. Blocks of Sequences of statements:
 Use order arithmetic addition rule which is O( f(n)+g(n)) = max( f(n), g(n)) and
add the time complexities of each statement.

Page 7
Data Structures & Algorithm – Lecture Notes

Examples of Formal and Informal way of complexity analysis:-


I = 0;
P = 1; // 2clock times for assignment
while(I < N)do // N testing conditions in the while loop
{
P = P * I; //(N) assignments & multiplications
I = I + 1; //(N) assignments & additions
}

A) Informal way of complexity analysis(constructing the complexity function T(n)using


informal way)
T(n) = 2 + (N) * (2 + 2)+ N
T(n) = 2 + (N) * (4) + N
T(n) = 5N - 2
B) Formal way of complexity analysis(constructing the complexity function T(n)using
formal way)

By ignoring the initializations and the loop conditionals, we construct the complexity function
as:- the four operations(two assignments, *&+)are executed N times i.e.
P = P * I; //(N) assignments & multiplications totally 4 operations, n-times executed
I = I + 1; //(N) assignments & additions

Therefore,
T(n) = N * 4
T(n) = 4N

So, the complexity function T(n) of the given algorithm is T(n) = 5N – 2 using the
informal method and T(n) = 4N using the formal way.

But in both methods, the Order/category of T(n)is linear time O(n) which is termed as
a good efficiency.

Example2:
int count ( )
{
int k=0; // 1 assignment
cout << “ Enter an integer”; //1 out put
Cin >>n; //1 for input

For (i=0;i<n; i++) //1 assignment,n increments, n+1 tests


K++; //n increment inside the loop

Return 0; //1 for return statement


}
T(n)= 1+1+1+(1+n+1+n)+n+1 = 3n+5 ----Informal way
T(n)= n*1 = n ----Formal way
But in both cases, order/category of T(n) is O(n).

Page 8
Data Structures & Algorithm – Lecture Notes

Exercises:
Compute the resources requirement in terms of input function T(n) and determine the
complexity the function.

Asymptotic Notation of category of complexity of algorithm


If we have a complexity function T(n) of an algorithm, then the order of its complexity in terms of
asymptotic notation can be given as follows:
o If growth rate of T(n) is less than or equal to f(n), then order of the algorithm is said to be
T(n)=O(f(n)). This is read as “T(n) is Big-oh of f(n)”

o If growth rate of T(n) is greater than or equal to f(n), then order of the algorithm is said to
be T(n)= Ω(f(n)). This is read as “T(n) is Big-omega of f(n)”

o If growth rate of T(n) is the same as f(n), then order of the algorithm is said to be T(n)=
Θ(f(n)). This is read as “T(n) is Theta of f(n)”

o If growth rate of T(n) is less than f(n), then order of the algorithm is said to be
T(n)=o(f(n)). This is read as “T(n) is little-oh of f(n)”

Page 9
Data Structures & Algorithm – Lecture Notes

o If growth rate of T(n) is greater than f(n), then order of the algorithm is said to be T(n)=
ω(f(n)). This is read as “T(n) is little-omega of f(n)”

T(n)= Θ(f(n))
T(n)= f(n)
T(n)=O(f(n))
T(n)=O(f(n))
T(n) < f(n)
T(n)<=f(n) T(n)

T(n)= ω(f(n))
T(n)>f(n)
T(n)= Ω(f(n))
T(n)>=f(n)

The following figure shows most commonly used asymptotes (growth functions) in asymptotic
notation.

2n n3
n2

nlogn
2n
n

logn

Fig. most commonly used asymptotes in asymptotic notation

Asymptotic notation is used to denote category of an algorithm in terms of asymptote. It is concerned


with how the running time of an algorithm increases as the size of input increases without
bound i.e. worst-case analysis. There are five notations used to describe running time of a function.
These are:
 Big-Oh Notation (O)
 Big-Omega Notation (Ω)

Page 10
Data Structures & Algorithm – Lecture Notes

 Theta Notation (Θ)


 Little-o Notation (o)
 Little-Omega Notation (ω)

These are the notations that are used to represent and explain the growth rate of complexity
function, T(n), of algorithms in terms of algorithms.

The Big-Oh Notation


 If f is a given function, and if some function g is an upper bound for f, then we say that f is
big O of g, which is written as f(n)=O(g(n)).
 In simple terms, f(n)=O(g(n)) means that “growth rate of f(n) is less than or equal to
g(n)”, which indicates that g(n) is upper bound of f(n).
 If f is O(g) - f is order g
o g is an upper bound on the value of f
o f grows at most as fast as g
o f ∊ O(g)
o f = O(g)

Big Omega notation


 If f is a given function, and if some function g is an lower bound for f, then we say that f is
big Ω of g, which is written as f(n)= Ω(g(n)).
 In simple terms, f(n)= Ω(g(n)) means that “growth rate of f(n) is greater than or equal to
g(n)”, which indicates that g(n) is lower bound of f(n).
 Example:
o If f(n) =n2, then f(n)= Ω( n)
o f(n) =3n+5, f(n) is O(n2) this implies that n2 is Ω(f(n))

Note: f(n) is Ω(g(n)) if and only if g(n) is O(f(n)).

Theta notation
 If f is a given function, and if some function g is a tight bound for f, then we say that f is Θ
of g, which is written as f(n)= Θ(g(n)).
 In simple terms, f(n)= Ω(g(n)) means that “growth rate of f(n) is the same as g(n) or f(n)
and g(n) has the same growth rate”, which indicates that g(n) is tight bound of f(n).
 If f(n) is Θ(g(n))
o f has an order of magnitude g
o g is asymptotically tight bound for f(n)
o f is an order of g
o f and g grow at the same rate for large n
o f and g have the same rate of growth

Note: properties of Θ:
 Reflexive: f(n)= Θ(f(n))
 Transitive : T(n)= Θ(f(n)), f(n)= Θ(g(n)) => T(n)= Θ(g(n))
Page 11
Data Structures & Algorithm – Lecture Notes

 Symmetry: T(n)= Θ(f(n)) iff f(n)= Θ(T(n))

Example:
 f ( n) = 2n2+3n+5
f(n) is O(n2) and also f(n) is Ω(n2). Therefore, f(n) is Θ(n2)
 If f(n)=2n+1, then f(n) = Θ (n)
 f(n) =2n2 then f(n)=O(n4), f(n)=O(n3), and f(n)=O(n2)
All these are technically correct, but the last expression is the best and tight one. Since 2n 2
and n2 have the same growth rate, it can be written as f(n)= Θ(n2) .

Little-o Notation
 If f is a given function, and if some function g is non-tight upper bound for f, then we say
that f is f is little-o of g, which is written as f(n)= o(g(n)).
 In simple terms, f(n)= o(g(n)) means that “f(n) has less growth rate compared to g(n) or
growth rate of f(n) is the less than g(n)”, but not greater than or equal to which is for big-
O.
Example:
 The function f(n)=3n+4 is o(n2)
 if we have g(n)= 2n2
g(n) =o(n3), O(n2), but g(n) is not o(n2).
 If we have f(n)= 3n, g(n)= n2, then f(n)= O(n2) But f(n)≠Ω(n2) Therefore, f(n)= o(n2)
Note: Little-oh is not reflexive, symmetric but it is transitive

Little-Omega (ω notation)
 If f is a given function, and if some function g is non-tight lower bound for f, then we say
that f is f is little-omega of g, which is written as f(n)= ω(g(n)).
 In simple terms, f(n)= ω(g(n)) means that “f(n) has greater growth rate compared to g(n) or
growth rate of f(n) is the greater than g(n)”, but not greater than or equal to which is for
big-omega.
Example:
 2n2=ω(n) but it’s not ω (n2).

Relational Properties of the Asymptotic Notations


Transitivity
• if f(n)=Θ(g(n)) and g(n)= Θ(h(n)) then f(n)=Θ(h(n)),
• if f(n)=O(g(n)) and g(n)= O(h(n)) then f(n)=O(h(n)),
• if f(n)=Ω(g(n)) and g(n)= Ω(h(n)) then f(n)=Ω (h(n)),
• if f(n)=o(g(n)) and g(n)= o(h(n)) then f(n)=o(h(n)), and
• if f(n)=ω (g(n)) and g(n)= ω(h(n)) then f(n)=ω (h(n)).
Symmetry
• f(n)=Θ(g(n)) if and only if g(n)=Θ(f(n)).
Transpose symmetry
• f(n)=O(g(n)) if and only if g(n)=Ω(f(n)),

Page 12
Data Structures & Algorithm – Lecture Notes

• f(n)=o(g(n)) if and only if g(n)=ω(f(n)).

Reflexivity
• f(n)=Θ(f(n)),
• f(n)=O(f(n)),
• f(n)=Ω(f(n)).
Conclusion

Page 13
Data Structures & Algorithm – Lecture Notes

CHAPTER 2 – Simple Sorting and Searching Algorithms

Chapter overview

 Simple Sorting algorithms


o Selection sort
o Bubble sort
o Insertion sort
 Simple searching algorithms
o Linear/sequential searching
o Binary searching

Sorting algorithms

Sorting
o process of reordering a list of items in either increasing or decreasing order.
o efficiency of sorting algorithm is measured using
o the number of comparisons and
o the number of data movements made by the algorithms.
o Sorting algorithms are categorized as:
o simple/elementary and
o advanced.
o Simple sorting algorithms, like Selection sort, Bubble sort and Insertion sort, are
only used to sort small-sized list of items.

1. Selection Sort
Given an array of length n,
 Search elements 0 through n-1 and select the smallest
 Swap it with the element in location 0
 Search elements 1 through n-1 and select the smallest
 Swap it with the element in location 1
 Search elements 2 through n-1 and select the smallest
 Swap it with the element in location 2
 Search elements 3 through n-1 and select the smallest
 Swap it with the element in location 3
 Continue in this fashion until there’s nothing left to search

i.e. the basic idea is


 Loop through the array from i=0 to n-1.
 Select the smallest element in the array from i to n
 Swap this value with value at position i.

Page 14
Data Structures & Algorithm – Lecture Notes

We repeatedly find the next largest (or smallest) element in the array and move it to its final
position in the sorted array.

Note: the list/array is divided into two parts:


 the sub-list of items already sorted and
 the sub-list of items remaining to be sorted

Analysis:
 The outer loop executes n-1 times
 The inner loop executes about n(n-1)/2 times on average (from n to 2 times)
 Work done in the inner loop is constant (swap two array elements)
 Time required is roughly (n-1)*[n(n-1)/2]
 You should recognize this as O(n2)
i.e.
 How many comparisons?
 (n-1)+(n-2)+…+1 = O(n2)
 How many swaps?
 n = O(n)

Code for selection sort

void selectionSort(int[] a)
{
int outer, inner, min;
for (outer = 0; outer < a.length - 1; outer++)
{
min=outer;
for (inner = outer + 1; inner < a.length; inner++)
{
if (a[inner] < a[min])
{
min=inner;
}
}
Int temp=a[outer];
a[outer]=a[min];
a[min] = temp;
}
}//end of function

Example 1:
#include<iostream>
using namespace std;
main()
{
int arr[5];
int mini,temp;
cout<<"Enter 5 numbers: "<<endl;
for(int i=0; i<5; i++) {
cin>>arr[i];
}

Page 15
Data Structures & Algorithm – Lecture Notes

cout<<"Original entered numbers: "<<endl;


for(int j=0; j<5; j++)
{
cout<<arr[j];
cout<<endl;
}
for(int r1=0;r1<4;r1++)
{
mini=r1;
for(int r2=r1+1; r2<5; r2++)
if(arr[r2]<arr[mini])
mini=r2;
if(mini !=r1)
{
temp=arr[r1];
arr[r1]=arr[mini];
arr[mini]=temp;
}}
cout<<"Array sorted by selection sort is: "<<endl;
for(int q=0; q<5; q++)
{
cout<<arr[q];
cout<<endl;
}}

2. Bubble Sort
Also called Exchange sort
simplest algorithm to implement and the slowest algorithm on very large inputs.
Basic Idea:
 Loop through array from i=0 to n and swap adjacent elements if they are out of order.
Repeatedly compares adjacent elements of an array.
 Compare each element (except the last one) with its neighbor to the right
 If they are out of order, swap them
 This puts the largest element at the very end
 The last element is now in the correct and final place
 Compare each element (except the last two) with its neighbor to the right
 If they are out of order, swap them
 This puts the second largest element next to last
 The last two elements are now in their correct and final places
 Compare each element (except the last three) with its neighbor to the right
 Continue as above until you have no unsorted elements on the left
Code for Bubble Sort

void bubbleSort(int[] a)
{
int outer, inner;
for (outer = a.length - 1; outer > 0; outer--) //counts down
{
for (inner = 0; inner < outer; inner++)

Page 16
Data Structures & Algorithm – Lecture Notes

{
if (a[inner] > a[inner + 1])
{
int temp = a[inner];
a[inner] = a[inner + 1];
a[inner + 1] = temp;
}
} }}

Example 2:
#include<iostream>
using namespace std;
main()
{
const int array_size = 4;
int x[array_size], hold;

cout<<"Enter 4 numbers: \n";


for (int i = 0 ; i <= 3 ; i++ )
cin>>x[i];
cout<<"\nOriginal Numbers:\n";
for ( int i=0; i<=3 ; i++)
cout<<x[i]<<" ";
for (int passes = 0; passes < array_size - 1; passes++)
{
for (int j = 0; j < array_size - passes - 1; j++)
{
if (x[j] > x[j+1])
{
hold = x[j];
x[j] = x[j+1];
x[j+1]=hold;
}
}
}
cout<< "\nAscending Order:\n";
for (int i = 0; i<4 ; i++)
cout<< x[i]<<" ";
cout<<endl<<endl;
}

Analysis for Bubble Sort


Let n = a.length = size of the array
The outer loop is executed n-1 times
Each time the outer loop is executed, the inner loop is executed
 Inner loop executes n-1 times at first, linearly dropping to just once
 On average, inner loop executes about n(n-1)/2 times for each execution of the outer
loop
 In the inner loop, the comparison is always done (constant time), the swap might be
done (also constant time)
Page 17
Data Structures & Algorithm – Lecture Notes

Result is (n-1) * [ n(n-1)/2 ] + k, that is, O(n2)


i.e.
 How many comparisons?
 (n-1)+(n-2)+…+1= O(n2)
 How many swaps?
 (n-1)+(n-2)+…+1= O(n2)

3. Insertion sort
It inserts each item into its proper place in the final list.
The simplest implementation of this requires two list structures – the source list and the list
into which sorted items are inserted.

Basic Idea:
 Find the location for an element and move all others up, and insert the element.
The approach is the same approach that we use for sorting a set of cards in our hand.
 While playing cards, we pick up a card, start at the beginning of our hand and find the
place to insert the new card, insert it and move all the others up one place.

The algorithm is as follows


 The left most value can be said to be sorted relative to itself. Thus, we don’t need to
do anything.
 Check to see if the second value is smaller than the first one. If it is, swap these two
values. The first two values are now relatively sorted.
 Next, we need to insert the third value in to the relatively sorted portion so that after
insertion, the portion will still be relatively sorted.
 Remove the third value first. Slide the second value to make room for insertion. Insert
the value in the appropriate position.
 Now the first three are relatively sorted.
 Do the same for the remaining items in the list.

Sort: 34 8 64 51 32 21
 34 8 64 51 32 21
 The algorithm sees that 8 is smaller than 34 so it swaps.
 8 34 64 51 32 21
 51 is smaller than 64, so they swap.
 8 34 51 64 32 21
 The algorithm sees 32 as another smaller number and moves it to its
appropriate location between 8 and 34.
 8 32 34 51 64 21
 The algorithm sees 21 as another smaller number and moves into between 8
and 32.
 Final sorted numbers:
 8 21 32 34 51 64

Code for Insertion sort

void insertionSort (int[] array)


{
int inner, outer;

for (outer = 1; outer < array.length; outer++)

Page 18
Data Structures & Algorithm – Lecture Notes

{
int temp = array[outer];
inner = outer;
while (inner > 0 && array[inner - 1] >= temp)
{
array[inner] = array[inner - 1];
inner--;
}
array[inner] = temp; }}

Example 3:
#include<iostream>
using namespace std;
void insertion(int array[], int n);
main(){
int array[20];
int n;
cout<<"How many numbers are you going to insert?"<<endl;
cin>>n;
cout<<"\nEnter "<<n<<" elements: ";
for(int i=0;i<n;i++){
cin>>array[i];
}
insertion(array, n);
}
void insertion(int array[], int n)
{
int a[30];
a[0] = array[0];
for (int i = 1; i < n; i++)
{
int temp = array[i];
int j = i - 1;
while (( a[j] > temp) && (j>=0))
{
a[j+1] = a[j];
j--;
}
a[j+1] = temp;
}
for (int k = 0; k < n; k++)
{
array[k] = a[k];
}
cout<<"The sorted numbers are: \n";
for(int z=0;z<n;z++){
cout<<array[z]<<endl;
}
}
Page 19
Data Structures & Algorithm – Lecture Notes

Analysis of Insertion sort


We run once through the outer loop, inserting each of n elements; this is a factor of n
On average, there are n/2 elements already sorted
 The inner loop looks at (and moves) half of these
 This gives a second factor of n/4
Hence, the time required for an insertion sort of an array of n elements is proportional to n2/4
Discarding constants, we find that insertion sort is O(n2)

i.e.
 How many comparisons?
 1+2+3+…+(n-1)= O(n2)
 How many swaps?
 1+2+3+…+(n-1)= O(n2)

Summary of sorting algorithms


Bubble Sort, Selection Sort, and Insertion Sort are all O(n2)
Within O(n2),
 Bubble Sort is very slow, and should probably never be used for anything
 Selection Sort is intermediate in speed
 Insertion Sort is usually the fastest of the three--in fact, for small arrays (like 10 or 15
elements), insertion sort is faster than more complicated sorting algorithms

Selection Sort and Insertion Sort are “good enough” for small arrays

Searching algorithms

Searching is a process of looking for a specific element in a list of items or determining that
the item is not in the list.

Two simple searching algorithms:


 Sequential / Linear Search, and
 Binary Search

1. Linear Searching

Also called sequential searching


Simplest type of searching process
 Easy to implement
 Can be used on very small data sets
 Not practical for searching large collections
The idea is:
 Loop through the array starting at the first/last element until the value of target
matches one of the array elements or until all elements are visited.
 If a match is not found, return –1
Analysis:
 Time is proportional to the size of input n
 time complexity O(n)

Page 20
Data Structures & Algorithm – Lecture Notes

Algorithm for Sequential/Linear Search


1. Initialize searcharray, key/number to be searched, length
2. Initialize index=0,
3. Repeat step 4 till index<=length.
4. if searcharray[index]=key
return index
else
increment index by 1.

Implementation of Linear Searching


int linearSearch(int list[ ], int key)
{
int index=0;
int found=0;
do
{
if(key==list[index])
found=1;
else
index++;
}while(found==0&&index<n);
if(found==0)
index=-1;
return index;
}

Example 1:
#include<iostream>
using namespace std;
main()
{
int arr1[5];
int req;
int location=-5;
cout<<"Enter 5 numbers to store in array: "<<endl;
for(int i=0; i<5; i++)
{
cin>>arr1[i];
}
cout<<endl;
cout<<"Enter the number you want to found :";
cin>>req;
cout<<endl;

for(int w=0;w<5;w++)
{
if(arr1[w] == req)
location=w;
}
if(location !=-5)

Page 21
Data Structures & Algorithm – Lecture Notes

{
cout<<"Required number is found out at the location:"<<location+1;
cout<<endl;
}
else
cout<<"Number is not found ";
}

2. Binary Searching
This searching algorithms works only on an ordered list.
It uses principle of divide and conquer
 Though additional cost has to do with keeping list in order, it is more efficient than
linear search
The basic idea is:
 Locate midpoint of array to search
 Determine if target is in lower half or upper half of an array.
 If in lower half, make this half the array to search
 If in the upper half, make this half the array to search
 Loop back to step 1 until the size of the array to search is one, and this element does
not match, in which case return –1.

Analysis:
 computational time for this algorithm is proportional to log2n
 Therefore the time complexity is O(log n)

Algorithm for Binary Search


1. Initialize an ordered array, searcharray, key, length.
2. Initialize left=0 and right=length
3. Repeat step 4 till left<=right
4. Middle =(left + right) / 2
5. if searcharray[middle]=key
Search is successful
return middle.
else
if key<searcharray[middle]
right=middle - 1
else
left=middle + 1.

Implementation of binary Searching

int Binary_Search(int list[ ],int k)


{
int left=0;
int right=n-1;
int found=0;
do{
mid=(left+right)/2;
if(key==list[mid])
found=1;
Page 22
Data Structures & Algorithm – Lecture Notes

else{
if(key<list[mid])
right=mid-1;
else
left=mid+1;
}
}while(found==0&&left<right);
if(found==0)
index=-1;
else
index=mid;
return index;
}

Example:
#include<iostream>
using namespace std;
main()
{
int a[100],n,i,beg,end,mid,item;
cout<<"\n------------ BINARY SEARCH ------------ \n\n";
cout<<"Enter No. of Elements= ";
cin>>n;
cout<<"\nEnter Elements:\n";
for(i=1;i<=n;i++)
{
cin>>a[i];
}
cout<<"\nEnter Item you want to Search= ";
cin>>item;
beg=1;
end=n;
mid=(beg+end)/2;
while(beg<=end && a[mid]!=item)
{
if(a[mid]<item)
beg=mid+1;
else
end=mid-1;
mid=(beg+end)/2;
}
if(a[mid]==item)
{
cout<<"\nData is Found at Location : "<<mid;
}
else
{
cout<<"Data is Not Found";
}}

Page 23
Data Structures & Algorithm – Lecture Notes

Analysis of growth functions


n^2 and n log n

2500

2000

1500
n log n
Time

n^2
1000

500

0
0 10 20 30 40 50 60
n

Analysis of growth functions

Page 24
Data Structures & Algorithm – Lecture Notes

CHAPTER 3 – LINKED LISTS

3.1 Review on Pointer, Dynamic Memory allocation and De-allocation Pointer


A pointer is a variable used for storing the address of a memory cell. This address is the location of
another object (typically another variable) in memory.
The general form for declaring a pointer variable:
type *name;
where type is the base type of the pointer and may be any valid type. The name of the pointer variable
is specified by name.

The Pointer Operators

& - is a unary operator that returns the memory address of its operand.
m = &count;
* - is an unary operator that returns the value located at the address that follows.
q = *m;
Example1:
#include<iostream>
using namespace std; OUTPUT:
main()
{ 100
int a = 100; 0x23fd6c
int *p = &a;
0x23fd6c 100
cout << a << endl;
0x23fd6c
cout << &a << endl;
cout << p << " " << *p << endl;
cout << &p << endl;

Example2:
#include<iostream> OUTPUT:
using namespace std;
int var = 1; Direct access, var = 1
int *ptr; Indirect access, var = 1
main() {
ptr = &var;
The address of var = 0x489010
cout<<"\nDirect access, var = "<<var;
cout<<"\nIndirect access, var = "<<*ptr; The address of var = 0x489010
cout<<"\n\nThe address of var = "<<&var;
cout<<"\nThe address of var = "<<ptr; }

Pointers and Arrays


Example:
#include<iostream>
using namespace std;
main() { OUTPUT:
int a[5] = {2,4,6,8,22};
int *p = a;
22
int i = 0;
cout << a[i] << " " << *p; }

Page 25
Data Structures & Algorithm – Lecture Notes

Arrays of Pointers

The declaration for an int pointer array of size 10 is


int *x[10];
To assign the address of an integer variable called var to the third element of the pointer
array, write
x[2] = &var;
To find the value of var, write
*x[2];

Dynamic Memory allocation and De-allocation

When a program is compiled, the size of the data the program will need to handle is often an unknown
factor; in other words there is no way to estimate the memory requirements of the program. In cases
like this you will need to allocate memory dynamically, that is, while the program is running.
Dynamically allocated memory can be released to continually optimize memory usage with respect
to current requirements. This in turn provides a high level of flexibility, allowing a programmer to
represent dynamic data structures, such as trees and linked lists.
C++ uses the new and delete operators to allocate and release memory, and this means that objects of
any type can be created and destroyed.

new Operator
The new operator is an operator that expects the type of object to be created as an argument.
In its simplest form, a call to new follows this syntax
Syntax:
ptr = new type;
Where ptr is a pointer to type. The new operator creates an object of the specified type and returns the
address of that object. The address is normally assigned to a pointer variable.

Example1:
#include<iostream> OUTPUT:
using namespace std;
main() 7
{
int *pnValue = new int; // dynamically allocate an integer
*pnValue = 7; // assign 7 to this integer
cout<<*pnValue;
} OUTPUT:

Example2: 1000
#include<iostream>
using namespace std;
main(){
double *pld = new double;
Page 26
Data Structures & Algorithm – Lecture Notes

pld = new double(10000);


cout << *pld << endl;}

delete operator
Memory that has been allocated by a call to new can be released using the delete operator. A call to
delete follows this syntax.
Syntax:
delete ptr;
The operand ptr addresses the memory space to be released. But make sure that this memory space
was dynamically allocated by a call to new!

Example1:
#include<iostream>
using namespace std; OUTPUT:
main(){
int *pnValue = new int; // dynamically allocate an integer 7
*pnValue = 7; // assign 7 to this integer 0
cout<<*pnValue<<endl;

delete pnValue; // unallocate memory assigned to pnValue


pnValue = 0;
cout<<pnValue;}

Example2:
#include<iostream>
using namespace std; OUTPUT:
main()
{ 3956208 3956208
int *ptr, *p;
ptr = new int[100];
p = new int;
delete[] ptr;
delete p;
cout<<*p<<" "<<*ptr;}

Review on Structure

Structure is an aggregate data type built using elements of primitive data type. It is a user defined data
type.

Structure is defined as:


Syntax:
struct struct-tag {
Type1 member variable1;
Type2 member variable2;
….
Type n member variablen;
} variable-name1, variable-name2…;

Page 27
Data Structures & Algorithm – Lecture Notes

E.g.
struct Time1 {
int hours;
Int minute;
Int seconds;
} T1, T2…; // declare Time1 data type variables T1, T2 etc. at the time defining
// the user defined data type.

You can initialize structures at the time of declaration or other time in the program.

Syntax:
struct struct-tag {
Type1 member variable1;
Type2 member variable2;
….
Type n member variablen;
} variable-name1= {value of member variable1, value of member variable2 ….};

E.g.
student {
string LName;
int age;
int Id;
String department;
}Abebe = {“Kebede”, 24, 150, “computer science”};

Or

Student Abebe= {“Kebede”, 24, 150, “compute science”};

To modify or access member variable, dot operators are used

Syntax:
struct-tag.member variable;
E.g.
T1.hours=1;
T1.minute=30;
T1.seconds=00;

A structure value is considered as a single value. Therefore,


 We can assign structure values using the assignment operator.
E.g.
variable-name1= variable-name2;
T1 = T2;
 As regular variables it is possible to pass structure to function and also return from function.
E.g.
void printStudent (student studentName); // function prototype

main ()
{
printStudent(Mesfin); // function call in the program
}

Page 28
Data Structures & Algorithm – Lecture Notes

Example program:

#include <iostream>
using namespace std;
struct Person{
string name;
int age;
char gender;
};
main(){
Person p;
p.name = "Christopher";
p.age = 34;
p.gender = 'M';
cout << "Name: " << p.name << endl;
cout << "Age: " << p.age << endl;
cout << "Gender: " << p.gender << endl;
}

Pointers to structures

Like any other data type, structures can be pointed by its own data type of pointers.
E.g.
Student * Std;
Student Abebe;
Std = &Abebe;
The value of the pointer Std would be assigned to a reference to the object Abebe (its memory
address).
The arrow operator (->) or deference operator is used exclusively with pointers to objects with
members. It serves to access a member of an object to which we have referenced. i.e. To access a
member through a pointer, we append its name to the pointer’s name separated by arrow.
E.g.
Std->age; is equivalent to (* Std).age;

Both are to mean, we are accessing age member variable of structure pointed by a pointer called Std.
But * (std.age) which is equivalent to * std.age it mean that it evaluate value pointed by member
variable age of object Std.
In general the dot and deference operator summarized in a table as follow.

Page 29
Data Structures & Algorithm – Lecture Notes

Structure can be self referenced using pointer


E.g.
struct Node{
Data type1 data;
Data type2 data;
… Node * next; // Name given to pointer which point to Node data type
}

Example:
#include <iostream>
using namespace std;
struct Point{
int x;
int y;
};
main(){
Point* p = new Point;
p->x = 9;
p->y = 4;
cout << p->x << " " << p->y << endl;
}

3.2. Singly Linked Lists


Linked list structure is a collection of nodes storing data and links to other nodes. A node contains
some information useful for a specific application and a pointer to the next node. The most flexible
implementation is by using pointers. If a node of linked list structure has a link only to its successor in
the sequence of node, the list is called a single linked list.

head

A linked list is a data structure which can change during execution.


 Successive elements are connected by pointers.
 Last element points to NULL.
 If a list currently contains 0 nodes, it is the empty list
In this case the list head points to NULL
 It can grow or shrink in size during execution of a program.
 It can be made just as long as required.
 It does not waste memory space.

Page 30
Data Structures & Algorithm – Lecture Notes

Create a new node

1. Allocate memory for the new node:


newNode= new ListNode;

2. Initialize the contents of the node:


newNode->value = num;

3. Set the pointer field to NULL:


newNode->next = NULL;

Insertion – Adding node to the linked list

Insertion at the beginning:


The simplest strategy for adding an item is at the beginning of a list. To add at the beginning
 Allocate space for a new node
 Copy the item into it
 Make the new node’s next pointer point to the current head of the list and
 Make the head of the list point to the newly allocated node.
Adding at the beginning of a list is fast and efficient.

Page 31
Data Structures & Algorithm – Lecture Notes

node *q;
q=p;
p=new node;
p->data=x;
p->link=q;

Insertion at the end


The steps to add a new node to the end of the list
 Allocate a space for new node
 Copy the item in to it (initialize the node’s information member)
 Set the next member of new node to null
 Include the node in the list by making the next member of the last node of the list a pointer to
the newly created node.
 Assign the address of the new node to tail

node *q,*t;
if(p==NULL)
{
p=new node;
p->data=x;
p->link=NULL;

Page 32
Data Structures & Algorithm – Lecture Notes

}
else
{
q=p;
while(q->link!=NULL)
q=q->link;
t=new node;
t->data=x;
t->link=NULL;
q->link=t;
}

Insertion in a specified location


The step to add node in a specified location:
 Allocate space for new node
 Copy the information into it
 Keep track of two nodes to find the right place in the list to insert new node between them.
(find the right place to insert new node between previous and the node )
 Link new node between previous and the node

node *temp,*temp1;
temp=p;
if(temp1==NULL)
{
temp1= new node;
temp1->data=value;
temp1->link=NULL;
p=temp1;
return;
}
for(int i=0;((i<position)&&(temp->link!=NULL)) ;i++)
{
if(i==(position-1))
{
temp1= new node;
temp1->data= value;
temp1->link=temp->link;
temp->link=temp1;
}
temp=temp->link;
}

Page 33
Data Structures & Algorithm – Lecture Notes

Deletion – Deleting a node from the linked list

A node can be deleted from the head of the list, end of the list or from somewhere in the middle.
E.g. delete temp; // release from the memory pointed to by temp.

Delete a node from the start


 Make temporary pointer
 Point temporary pointer to first node “temp= head;”
 Move start pointer to the next node in the list
head= head->next; // second node in the list
 Delete temporary pointer (This delete original start node) “delete temp;”

node *q;
q=p;
if(q==NULL)
{
cout<<" \nNo data is present..";
return;
}
p=q->link;
delete q;
return;

Delete a node from the end


 Make temporary pointers
 Point temporary pointers to head and one to NULL
Prev = NULL;
Current = head;
 Advance head pointer until it becomes a reference to the last node and keep track of prev to
node before head.
 Unlike prev node from current node and make pre->next to NULL
prev->next= NULL;
 Delete current node

node *q,*t;
q=p;
if(q==NULL) {
cout<<" \nThere is no data in the list..";
return; }
if(q->link==NULL) {
p=q->link;
delete q;
return; }
while(q->link->link!=NULL)
q=q->link;
q->link=NULL;
return; }
list::~list() {
node *q;

Page 34
Data Structures & Algorithm – Lecture Notes

if(p==NULL) return;
while(p!=NULL)
{
q=p->link;
delete p;
p=q;
}

Delete a specific node from a list


If the list has several nodes that match the specified value, the version of the function presented here
will remove only the first one it finds.
 Declare pointers to point to keep pointers to the previous and the current nodes
 Find the node to be removed (keep track of the previous node)
Advance “head” until it becomes a reference to the pointer to the node to be removed.
 Unlink the node and connect the previous node directly to next node.
 Delete the unlinked node

Page 35
Data Structures & Algorithm – Lecture Notes

node *q,*r;
q=p;
if(q->data==x) {
p=q->link;
delete q;
return; }
r=q;
while(q!=NULL) {
if(q->data==x) {
r->link=q->link;
delete q;
return; }
r=q;
q=q->link; }
cout<<"\n Element u entered "<<x<<" is not found..";

Traversing a Linked List


A procedure that access and processes all elements of a data structure in sequence is called traversal.
To display a list nodes
 Visit each node in a linked list:
display contents, validate data, etc.
 Basic process:
–set a pointer to the contents of the head pointer
–while pointer is not NULL
o process data
o go to the next node by setting the pointer to the pointer field of the current node in the
list
–end while

Page 36
Data Structures & Algorithm – Lecture Notes

node *q;
q=p;
if(q==NULL) {
cout<<" \nNo data is in the list..";
return; }
cout<<" \nThe items present in the list are :";
while(q!=NULL) {
cout<<" "<<q->data;
q=q->link; }

Page 37
Data Structures & Algorithm – Lecture Notes

CHAPTER 4 – STACKS AND QUEUES

4.1 Stack

Stack is a data structure provides temporary storage in such a way that the element stored last
will be retrieved first (last in first out or LIFO method). Items can be inserted (“pushed”) and
deleted (“popped”), but only the most recently inserted element can be operated on. This
element is called “Top” of the stack.
Stack is useful for temporary storage, especially for dealing with nested structure or
processes; expressions with in expressions, functions calling other functions, directories with
in directories.
Operations:
 Push(s, k):- push an item k into stack s.
 Pop(s):- deleting the top element, returning its value
 Peek(s):- returning the value of the top elements
 IsEmpty(s):- return true if and only if the stack is empty
 Create(s):- make an empty stack (remove existing items from the stack
initialize the stack to empty)
 Isfull(): return true if and only if the stack is full

The Basic Operations:


Push()
{
if there is room {
put an item on the top of the stack
else
give an error message
}}
Pop()
{
Page 38
Data Structures & Algorithm – Lecture Notes

if stack not empty {


return the value of the top item
remove the top item from the stack
}
else {
give an error message } }
CreateStack()
{
remove existing items from the stack
initialize the stack to empty
}

Array Implementation of Stacks: The PUSH operation

Algorithm:
Step-1: Increment the Stack TOP by 1. Check whether it is always less than the Upper
Limit of the stack. If it is less than the Upper Limit go to step-2 else report -"Stack
Overflow"
Step-2: Put the new element at the position pointed by the TOP

Array Implementation of Stacks: the POP operation

Algorithm:
Step-1: If the Stack is empty then give the alert "Stack underflow" and quit; or else go
to step-2
Step-2:
a) Hold the value for the element pointed by the TOP
b) Put a NULL value instead
c) Decrement the TOP by 1

Sample program:

#include<iostream>
#include<stdlib.h>
using namespace std;
int const MAX=5;
int top=-1;
int stack[MAX];
void push();
void pop();
void peek();
void display();
main()
{
int ch;
while(true)
{
cout<<"\n1.Push\n2.Pop\n3.Display\n4.Peek\n5.Exit\n";
cout<<"Choose your option\n";
cin>>ch;
switch(ch) {
case 1:push();
break;

Page 39
Data Structures & Algorithm – Lecture Notes

case 2:pop();
break;
case 3:display();
break;
case 4: peek();
break;
case 5:exit(0);
} }}

void push() {
int element;

if(top==MAX-1) {
cout<<"Stack overflow\n";
return;
}
else {
cout<<"Enter the element\n";
cin>>element;
top=top+1;
stack[top]=element;
cout<<"Element added\n"<<element;
}}

void pop() {
int element;
if(top<0) {
cout<<"Stack underflow\n";
return;
}
else {
element=stack[top];
top=top-1;
cout<<"Element is removed\n"<<element;
}}

void display() {
int i;
if(top<0) {
cout<<"Empty stack\n";
return;
}
else {
cout<<"Stack is\n";
for(i=top;i>=0;i--)
cout<<"\n"<<stack[i];
}}
void peek() {
cout<<"The value in top is: "<<stack[top]; }

Page 40
Data Structures & Algorithm – Lecture Notes

4.2 Queue

 Is a data structure that has access to its data at the front and rear.

 Three kinds
 Normal queue, usualy called queue
 Prority queue
 Doublly-Ended queue(Dequeue)
 operates on FIFO (Fast In First Out) basis.
 uses two pointers/indices to keep track of information/data.
 has two basic operations:
 enqueue - inserting data at the rear of the queue
 dequeue – removing data at the front of the queue
dequeue enqueue

Front Rear/back

Simple array implementation of enqueue and dequeue operations

Analysis:
Consider the following:
 an array of size MAX_SIZE , int num[MAX_SIZE];
 We need to have two integer variables that tell:
o the index of the front element int FRONT =-1;
o the index of the rear element int REAR =-1;

 We also need an integer variable that tells:


o the total number of data in the queue, int QUEUESIZE=0;

Page 41
Data Structures & Algorithm – Lecture Notes

Sample program:

#include<iostream>
#include<stdlib.h>
using namespace std;

int const MAX=5;


void enqueue();
void dequeue();
void display();
void peek();
int rear=-1;
int front=-1;
int queue[MAX];
main()
{
int ch;
while(true)
{
cout<<"\n1.Enqueue\n2.Dequeue\n3.Display\n4.Peek\n5.Exit\n";
cout<<"\nChoose your option\n";
cin>>ch;
switch(ch)
{
case 1: enqueue();
break;
case 2: dequeue();
break;
case 3: display();
break;
case 4: peek();
break;
case 5: exit(0);
}

}
}
void enqueue(){
int element;
if((rear-front)==MAX-1)
cout<<"Queue overflow\n";
else
{
if(front==-1)
front=0;
cout<<"Enter the new element\n";
cin>>element;
rear=rear+1;
queue[rear]=element;
cout<<"Element is added\n"<<element;
}}
void dequeue()

Page 42
Data Structures & Algorithm – Lecture Notes

{
int i;
if(front==-1||front>rear)
{
cout<<"Queue underflow\n";
return;
}
else
{
cout<<"Element is deleted from queue\n"<<queue[front];

for(i=front;i<=rear;i++)
{
queue[i]=queue[i+1];
}
rear=rear-1;
}
}
void display()
{
int i;
if(front==-1||front>rear)
{
cout<<"Queue is empty\n";
}
else
{
cout<<"Queue\n";
for(i=front;i<=rear;i++)
{
cout<<"\n"<<queue[i];
}
}
}
void peek(){
cout<<"The value in front is: "<<queue[front];
}

Page 43
Data Structures & Algorithm – Lecture Notes

CHAPTER 5 – TREE STRUCTURES

A tree is a set of nodes and edges that connect pairs of nodes. It is an abstract model of a
hierarchical structure. Rooted tree has the following structure:
 One node distinguished as root.
 Every node C except the root is connected from exactly other node P. P is C's parent,
and C is one of C's children.
 There is a unique path from the root to the each node.
 The number of edges in a path is the length of the path.

Tree Terminologies

Consider the tree below:

Root: a node without a parent. A


Internal node: a node with at least one child. A, B, F, J
External (leaf) node: a node without a child. C, D, E, H, L, M, G
Ancestors of a node: parent, grandparent, grand-grandparent, etc of a node.
Ancestors of L A, F, J
Descendants of a node: children, grandchildren, grand-grandchildren etc of a node.
Descendants of F H, J, L, M
Depth of a node: number of ancestors or length of the path from the root to the node.
Depth of H 2

Page 44
Data Structures & Algorithm – Lecture Notes

Height of a tree: depth of the deepest node. 3


Subtree: a tree consisting of a node and its descendants.
Binary tree: a tree in which each node has at most two children called left child and right child.

Full binary tree: a binary tree where each node has either 0 or 2 children.

Balanced binary tree: a binary tree where each node except the leaf nodes has left and right children
and all the leaves are at the same level.

Complete binary tree: a binary tree in which the length from the root to any leaf node is either h or
h-1 where h is the height of the tree. The deepest level should also be filled from left to right.

Binary search tree (ordered binary tree): a binary tree that may be
empty, but if it is not empty it satisfies the following.
 Every node has a key and no two elements have the same key.

Page 45
Data Structures & Algorithm – Lecture Notes

 The keys in the right subtree are larger than the keys in the root.
 The keys in the left subtree are smaller than the keys in the root.
 The left and the right subtrees are also binary search trees.

Data Structure of a Binary Tree


struct DataModel
{
Declaration of data fields
DataModel * Left, *Right;
};
DataModel *RootDataModelPtr=NULL;

Operations on Binary Tree

Consider the following definition of binary search tree.


struct Node
{
int Num;
Node * Left, *Right;
};
Node *RootNodePtr=NULL;

 Insertion
When a node is inserted the definition of binary search tree should be preserved.
Suppose there is a binary search tree whose root node is pointed by RootNodePtr and we want
to insert a node (that stores 17) pointed by InsNodePtr.

Case 1: There is no data in the tree (i.e. RootNodePtr is NULL)


- The node pointed by InsNodePtr should be made the root node.

Case 2: There is data


- Search the appropriate position.
- Insert the node in that position.

Page 46
Data Structures & Algorithm – Lecture Notes

Implementation for insertion:


void Binary_tree::insert1(int n){
tree *temp=root,*newnode;
newnode=new tree;
newnode->Left=NULL;
newnode->Right=NULL;
newnode->info=n;
root=insert2(temp,newnode); }

 Traversing

Binary search tree can be traversed in three ways.


a. Pre order traversal - traversing binary tree in the order of parent, left and right.
b. Inorder traversal - traversing binary tree in the order of left, parent and right.
c. Postorder traversal - traversing binary tree in the order of left, right and parent.

Example:

Page 47
Data Structures & Algorithm – Lecture Notes

Implementation of traversal:
void Binary_tree::pretrav(tree *t = root){
if(root == NULL){
cout<<"Nothing to display";
}else
if(t != NULL){
cout<<t->info<<" ";
pretrav(t->Left);
pretrav(t->Right); } }
void Binary_tree::intrav(tree *t = root){
if(root==NULL){
cout<<"Nothing to display";
}else
if(t!=NULL){
intrav(t->Left);
cout<<t->info<<" ";
intrav(t->Right);
}}
void Binary_tree::posttrav(tree *t = root){
if(root==NULL){
cout<<"Nothing to display";
}else
if(t!=NULL){
posttrav(t->Left);
posttrav(t->Right);
cout<<t->info<<" "; }}

 Deletion

To delete a node (whose Num value is N) from binary search tree (whose root node is pointed by
RootNodePtr), four cases should be considered. When a node is deleted the definition of binary search
tree should be preserved.

Page 48
Data Structures & Algorithm – Lecture Notes

Case 2: Deleting a node having only one child, e.g. 2

Approach 1: Deletion by merging – one of the following is done


· If the deleted node is the left child of its parent and the deleted node has only the left child,
the left child of the deleted node is made the left child of the parent of the deleted node.
· If the deleted node is the left child of its parent and the deleted node has only the right child,
the right child of the deleted node is made the left child of the parent of the deleted node.
· If the deleted node is the right child of its parent and the node to be deleted has only the left
child, the left child of the deleted node is made the right child of the parent of the deleted
node.
· If the deleted node is the right child of its parent and the deleted node has only the right child,
the right child of the deleted node is made the right child of the parent of the deleted node.

Approach 2: Deletion by copying- the following is done


· Copy the node containing the largest element in the left (or the smallest element in the right)
to the node containing the element to be deleted
· Delete the copied node

Page 49
Data Structures & Algorithm – Lecture Notes

Page 50
Data Structures & Algorithm – Lecture Notes

Approach 2: Deletion by copying- the following is done


· Copy the node containing the largest element in the left (or the smallest element in the right)
to the node containing the element to be deleted
· Delete the copied node

Page 51
Data Structures & Algorithm – Lecture Notes

Case 4: Deleting the root node, 10


Approach 1: Deletion by merging- one of the following is done

Page 52
Data Structures & Algorithm – Lecture Notes

Approach 2: Deletion by copying- the following is done


· Copy the node containing the largest element in the left (or the smallest element in the right)
to the node containing the element to be deleted
· Delete the copied node

Page 53
Data Structures & Algorithm – Lecture Notes

Complete Code for the Implementation of a Binary Tree

#include<iostream>
#include<cstdlib>
using namespace std;
struct tree{
int info;
tree *Left, *Right;
};
tree *root;

class Binary_tree{
public:
Binary_tree();
void insert1(int);
tree *insert2(tree *, tree *);
void Delete(int);
void pretrav(tree *);
void intrav(tree *);
void posttrav(tree *);
};

Binary_tree::Binary_tree(){
root = NULL;
}
tree* Binary_tree::insert2(tree *temp,tree *newnode){
if(temp==NULL){
temp=newnode;
}
else if(temp->info < newnode->info){
insert2(temp->Right,newnode);
if(temp->Right==NULL)
temp->Right=newnode;
}
else{
insert2(temp->Left,newnode);
if(temp->Left==NULL)
temp->Left=newnode;
}
return temp;
}
void Binary_tree::insert1(int n){
tree *temp=root,*newnode;
newnode=new tree;
newnode->Left=NULL;
newnode->Right=NULL;
newnode->info=n;
root=insert2(temp,newnode);
}
void Binary_tree::pretrav(tree *t = root){
if(root == NULL){
cout<<"Nothing to display";
}else

Page 54
Data Structures & Algorithm – Lecture Notes

if(t != NULL){
cout<<t->info<<" ";
pretrav(t->Left);
pretrav(t->Right);
}
}
void Binary_tree::intrav(tree *t = root){
if(root==NULL){
cout<<"Nothing to display";
}else
if(t!=NULL){
intrav(t->Left);
cout<<t->info<<" ";
intrav(t->Right);
}
}
void Binary_tree::posttrav(tree *t = root){
if(root==NULL){
cout<<"Nothing to display";
}else
if(t!=NULL){
posttrav(t->Left);
posttrav(t->Right);
cout<<t->info<<" ";
}
}
void Binary_tree::Delete(int key)
{
tree *temp = root,*parent = root, *marker;
if(temp==NULL)
cout<<"The tree is empty"<<endl;
else{
while(temp!=NULL && temp->info!=key){
parent=temp;
if(temp->info<key){
temp=temp->Right;
}else{
temp=temp->Left;
}
}
}
marker=temp;
if(temp==NULL)
cout<<"No node present";
else if(temp==root){
if(temp->Right==NULL && temp->Left==NULL){
root=NULL;
}
else if(temp->Left==NULL){
root=temp->Right;
}
else if(temp->Right==NULL){
root=temp->Left;

Page 55
Data Structures & Algorithm – Lecture Notes

}
else{
tree *temp1;
temp1 = temp->Right;
while(temp1->Left!=NULL){
temp=temp1;
temp1=temp1->Left;
}
if(temp1!=temp->Right){
temp->Left=temp1->Right;
temp1->Right=root->Right;
}
temp1->Left=root->Left;
root=temp1;
}
}
else{
if(temp->Right==NULL && temp->Left==NULL){
if(parent->Right==temp)
parent->Right=NULL;
else
parent->Left=NULL;
}
else if(temp->Left==NULL){
if(parent->Right==temp)
parent->Right=temp->Right;
else
parent->Left=temp->Right;
}
else if(temp->Right==NULL){
if(parent->Right==temp)
parent->Right=temp->Left;
else
parent->Left=temp->Left;
}else{
tree *temp1;
parent=temp;
temp1=temp->Right;
while(temp1->Left!=NULL){
parent=temp1;
temp1=temp1->Left;
}
if(temp1!=temp->Right){
temp->Left=temp1->Right;
temp1->Right=parent->Right;
}
temp1->Left=parent->Left;
parent=temp1;
}
}
delete marker;
}
main(){

Page 56
Data Structures & Algorithm – Lecture Notes

Binary_tree bt;
int choice, n, key;
while(1){
cout<<"\n\t1. Insert\n\t2. Delete\n\t3. Preorder Traversal\n\t4. Inorder Treversal\n\t5. Postorder
Traversal\n\t6. Exit"<<endl;
cout<<"Enter your choice: ";
cin>>choice;
switch(choice){
case 1:
cout<<"Enter item: ";
cin>>n;
bt.insert1(n);
break;
case 2:
cout<<"Enter element to delete: ";
cin>>key;
bt.Delete(key);
break;
case 3:
cout<<endl;
bt.pretrav();
break;
case 4:
cout<<endl;
bt.intrav();
break;
case 5:
cout<<endl;
bt.posttrav();
break;
case 6:
exit(0);
}
}
}
//insert: 100 80 60 40 20 10 200 109 120 140 160 180

Page 57
Data Structures & Algorithm – Lecture Notes

CHAPTER 6 – Advanced Sorting & Searching Algorithm

 Advanced Sorting

1. Heap Sort

The heap sort algorithm uses the data structure called the heap. A heap is defined as a
complete binary tree in which each node has a value greater than both its children (if any).
Each node in the heap corresponds to an element of the array, with the root node
corresponding to the element with index 0 in the array. Considering a node corresponding to
index i, then its left child has index (2*i + 1) and its right child has index (2*i + 2). If any or
both of these elements do not exist in the array, then the corresponding child node does not
exist either. Note that in a heap the largest element is located at the root node.
It uses a process called "adjust to accomplish its task (building a heap tree) whenever a value
is larger than its parent.
The root of the binary tree (i.e., the first array element) holds the largest key in the heap. This
type of heap is usually called descending heap or mere heap, as the path from the root node to
a terminal node forms an ordered list of elements arranged in descending order. Fig. 6.1
shows a heap.

We can also define an ascending heap as an almost complete binary tree in which the value of
each node is greater than or equal to the value of its father. This root node has the smallest
element of the heap. This type of heap is also called min heap.

Page 58
Data Structures & Algorithm – Lecture Notes

Algorithm:
1. Construct a binary tree
· The root node corresponds to Data[0].
· If we consider the index associated with a particular node to be i, then the
left child of this node corresponds to the element with index 2*i+1 and the right child
corresponds to the element with index 2*i+2. If any or both of these elements do not
exist in the array, then the corresponding child node does not exist either.
2. Construct the heap tree from initial binary tree using "adjust" process.
3. Sort by swapping the root value with the lowest, right most value and
deleting the lowest, right most value and inserting the deleted value in the array in it
proper position.

Page 59
Data Structures & Algorithm – Lecture Notes

Page 60
Data Structures & Algorithm – Lecture Notes

C++ Implementation:
#include <iostream>
using namespace std;

void max_heapify(int *a, int i, int n)


{
int j, temp;
temp = a[i];
j = 2*i;
while (j <= n)
{
if (j < n && a[j+1] > a[j])
j = j+1;
if (temp > a[j])
break;
else if (temp <= a[j])
{
a[j/2] = a[j];
j = 2*j;
}}
a[j/2] = temp;
return;
}
void heapsort(int *a, int n)
{

Page 61
Data Structures & Algorithm – Lecture Notes

int i, temp;
for (i = n; i >= 2; i--)
{
temp = a[i];
a[i] = a[1];
a[1] = temp;
max_heapify(a, 1, i - 1);
}}
void build_maxheap(int *a, int n)
{
int i;
for(i = n/2; i >= 1; i--)
{
max_heapify(a, i, n);
}}
main()
{
int n, i, x;
cout<<"enter no of elements of array\n";
cin>>n;
int a[20];
for (i = 1; i <= n; i++)
{
cout<<"enter element"<<(i)<<endl;
cin>>a[i];
}
build_maxheap(a,n);
heapsort(a, n);
cout<<"sorted output\n";
for (i = 1; i <= n; i++)
{
cout<<a[i]<<endl; }}

2. Quick Sort

Quick sort is the fastest known algorithm. It uses divide and conquer strategy and in
the worst case its complexity is O (n2). But its expected complexity is O(nlogn).
The quick sort algorithm works by partitioning the array to be sorted. And each
partitions are internally sorted recursively. In partition the first element of an array is
chosen as a key value. This key value can be the first element of an array. That is, if A
is an array then key = A (0), and rest of the elements are grouped into two portions
such that,
(a) One partition contains elements smaller than key value
(b) Another partition contains elements larger than the key value

Algorithm:
1. Choose a pivot value (mostly the first element is taken as the pivot value)
2. Position the pivot element and partition the list so that:
· the left part has items less than or equal to the pivot value
Page 62
Data Structures & Algorithm – Lecture Notes

· the right part has items greater than or equal to the pivot value
3. Recursively sort the left part
4. Recursively sort the right part

Example: Sort the following list using quick sort algortihm

Page 63
Data Structures & Algorithm – Lecture Notes

C++ Implementation:
#include<iostream>
using namespace std;

int Partition(int a[], int beg, int end) //Function to Find Pivot Point
{
int p=beg, pivot=a[beg], loc;
for(loc=beg+1;loc<=end;loc++)
{
if(pivot>a[loc])
{
a[p]=a[loc];
a[loc]=a[p+1];
a[p+1]=pivot;
p=p+1;
}}
return p;
}
void QuickSort(int a[], int beg, int end)
{

Page 64
Data Structures & Algorithm – Lecture Notes

if(beg<end)
{
int p=Partition(a,beg,end); //Calling Procedure to Find Pivot
QuickSort(a,beg,p-1); //Calls Itself (Recursion)
QuickSort(a,p+1,end); //Calls Itself (Recursion)
}}
main()
{
int a[100],i,n,beg,end;
cout<<"\n------- QUICK SORT -------\n\n";
cout<<"Enter the No. of Elements : ";
cin>>n;
for(i=1;i<=n;i++)
{
cin>>a[i];
}
beg=1;
end=n;
QuickSort(a,beg,end); //Calling of QuickSort Function
cout<<"\nAfter Sorting : \n";
for(i=1;i<=n;i++)
{
cout<<a[i]<<endl;
}}

3. Merge Sort

Like quick sort, merge sort uses divide and conquer strategy and its time complexit y
is O(nlogn). It begins by dividing a list into two sublists, and then recursively divides
each of those sublists until there are sublists with one element each. These sublists are
then combined using a simple merging technique. In order to combine two lists, the
first value of each is evaluated, and the smaller value is added to the output list. This
process continues until one of the lists has become exhausted, at which point the
remainder of the other list is simply appended to the output list. Two closest lists are
combined at each end, until all the elements are merged back into a single list.

Algorithm:
1. Divide the array in to two halves.
2. Recursively sort the first n/2 items.
3. Recursively sort the last n/2 items.
4. Merge sorted items (using an auxiliary array).

Page 65
Data Structures & Algorithm – Lecture Notes

Example: Sort the following list using merge sort algorithm.

C++ Implementation:

#include<iostream>
using namespace std;

void mergesort(int[],int,int);
void merge(int[],int,int,int);
main()
{
int a[10],p,q,r,i,n;
cout<<"Enter the number of elements";
cin>>n;
p=0;
r=n-1;
cout<<"Enter the array";
for(i=0;i<n;i++) {
cin>>a[i];
}
mergesort(a,p,r);
cout<<"The sorted array is:";
for(i=0;i<n;i++) {
cout<<"\n"<<a[i];
Page 66
Data Structures & Algorithm – Lecture Notes

}}

void mergesort(int a[],int p,int r) {


if( p < r)
{
int q=(p+r)/2;
mergesort(a,p,q);
mergesort(a,q+1,r) ;
merge(a,p,q,r);
}
}
void merge(int a[],int p,int q,int r)
{
int c[10];
int i=p;
int j=q+1;
int k=p;
while((i<=q)&&(j<=r))
{
if(a[i]<a[j])
{
c[k]=a[i];
i=i+1;
k=k+1;
}
else
{
c[k]=a[j];
j=j+1;
k=k+1;
}
}
while(i<=q)
{
c[k] =a[i];
i=i+1;
k=k+1;
}
while(j<=r)
{
c[k]=a[j];
j=j+1;
k=k+1;
}
int l=p;
while(l<=r)
{
a[l]=c[l];
l=l+1;
}
}

Page 67
Data Structures & Algorithm – Lecture Notes

4. Shell Sort

Shell sort is an improvement of insertion sort. It is developed by Donald Shell in 1959.


Insertion sort works best when the array elements are sorted in a reasonable order. Thus, shell
sort first creates this reasonable order.

Algorithm:
1. Choose gap gk between elements to be partly ordered.
2. Generate a sequence (called increment sequence) gk, gk-1,…., g2, g1 where for each sequence
gi, A[j]<=A[j+gi] for 0<=j<=n-1-gi and k>=i>=1
Example: Sort the following list using shell sort algorithm.

C++ Implementation:
#include<iostream>
using namespace std;

void read(int a[10],int n)


{
cout<<"reading\n";
for(int i=0;i<n;i++)
cin>>a[i];
}
void display(int a[10],int n) {
for(int i=0;i<n;i++)
cout<<a[i]<<"\t"; }

void shellsort(int a[10],int n) {

Page 68
Data Structures & Algorithm – Lecture Notes

int gap=n/2;
do {
int swap;
do
{
swap=0;
for(int i=0;i<n-gap;i++)
if(a[i]>a[i+gap])
{
int t=a[i];
a[i]=a[i+gap];
a[i+gap]=t;
swap=1;
}}
while(swap); }
while(gap=gap/2); }
main()
{
int a[10];
int n;
cout<<"enter n\n";
cin>>n;
read(a,n);
cout<<"before sorting\n";
display(a,n);
shellsort(a,n);
cout<<"\nafter sorting\n";
display(a,n);
}

 Advanced Searching

Hashing is a technique where we can compute the location of the desired record in order
to retrieve it in a single access (or comparison).
Let there is a table of n employee records and each employee record is defined by a
unique employee code, which is a key to the record and employee name. If the key (or
employee code) is converted into array index also called hash code, then the record can be
accessed by the key directly.As in the following example.

Page 69
Data Structures & Algorithm – Lecture Notes

The array indices are called hash codes. The table containing the hash codes is called hash
table. And process of converting the keys(item or employee code) into hash code/array
index is called hashing. The function or the formula used to convert keys into hash code
is called hash function.
It is possible that two different keys k1 and k2 will yield the same hash address/code. This
situation is called Hash Collision.

Hash Function
The basic idea of hash function is the transformation of the key into the corresponding
location in the hash table. A Hash function H can be defined as a function that takes key
as input and transforms it into a hash table index. Hash functions are of two types:
1. Distribution- Independent function
2. Distribution- Dependent function
Following are the most popular Distribution - Independent hash functions:
1. Division method
2. Mid Square method
3. Folding method
Division Method
The hash function H is defined by:
H(k)= k(mod m), where:

Page 70
Data Structures & Algorithm – Lecture Notes

 the number m is usually chosen to be prime number to minimize the


collision and it is larger than the number of keys k & the total number of
records/rows in the TABLE.
H(k) is the hash address (or index of the array) and here k (mod m) means the remainder
when k is divided by m.
 For example:
Let a company has 90 employees and 00, 01, 02,.... 99 be the two digit 100
memory address (or index or hash address) to store the records. We have
employee code as the key. Choose m in such a way that it is greater than
90. Suppose m = 93. Then for the following employee code (or key k):
H(k) = H(2103) = 2103 (mod 93) = 57
H(k) = H(6147) = 6147 (mod 93) = 9
H(k) = H(3750) = 3750 (mod 93) = 30
Then a typical employee hash table will look like as:

So if you enter the employee code to the hash function, we can directly retrieveTABLE[H(k)]
details directly.
Note: if the memory address begins with 01-m instead of 00- m, then we have to choose the
hash functionH (k) = k (mod m) + 1.

Page 71
Data Structures & Algorithm – Lecture Notes

Hash Collision

It is possible that two non-identical keys K1, K2 are hashed into the same hash
address/code. This situation is called Hash Collision.
Let us consider a hash table having 10 locations. Division method is used to hash the
key. H (k) = k (mod m), if m is chosen as 10. The Hash function produces any
integer between 0 and 9 inclusions, depending on the value of the key. If we want to
insert a new record with key 500 then H(500) = 500(mod 10) = 0. But the location 0
in the table may be already filled ( i.e., not empty). Thus collision occurred.
Collisions are almost impossible to avoid but it can be minimized considerably by
introducing any one of the following three techniques:
1. Open addressing
2. Chaining
3. Bucket addressing
Chaining technique

In chaining technique the entries in the hash table are dynamically allocated and entered
into a linked list associated with each hash key as in the following.

Page 72
Data Structures & Algorithm – Lecture Notes

Summary of order of efficiency of algorithms

Page 73

You might also like