0% found this document useful (0 votes)
90 views

Data Structures and Algorithms

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

Data Structures and Algorithms

Structures and Algorithms
Copyright
© © All Rights Reserved
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 61

Unit 1 – Overview of Data Structures

Overview:
In this course we will study data structures and learn how to write efficient programs. But this has
nothing to do with programming tricks but rather with good organization of information and good
algorithms that saves computer memory and running time.

Module Objectives:
After successful Completion of this module, you should be able to:

• Define and differentiate data structures, data type and abstract data type
• Discuss how one will select a data structure
• Describe the different data structures
• Knowledgeably converse about the concepts of abstract data types

Course Materials:

Learning about computers does not stop in learning how to do programs to run in them. It
is also important to know how data is represented in memory (data structure) and how it is
represented on the disk (file structure). 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 and sometimes efficient
way.

Doing efficient programs requires efficient data structures. By efficient we mean a problem
has to be solved within the given time and space constraints. Each problem puts constraints on
time and space. For example, in banking, opening or starting an account will require a few
minutes. Doing transactions, say withdrawing cash or depositing it, requires a few seconds. But
closing and account normally takes overnight. Here we see constraints on time and space. A
solution is efficient if it solves the problem within its space and time constraints. The cost of a
solution equates to the amount of resources consumed.

In order to solve problems efficiently one can do the following:

1. Analyze the problem to determine the resource constraints a solution must meet.
2. Determine the operations that must be supported (e.g., record search, insertion, deletion,
etc.)
3. Quantify the constraints for each operation (e.g., search operations must be very fast)
4. Select data structure that best meet these requirements.

1
Cost and Benefits

Each data structures requires spaces for each data item it stores, time to perform each
operation, and some programming effort to implement it. Every data structure has costs and
benefits. Rarely one data structure is better than another in all situations. One may permit faster
search (or insertion or deletion) operations than the other but that does not mean that it is better
than the rest. Note also if all operations are of the same importance.

Data Structures, Data Type and Abstract Data Type

Earlier we defined data structure as a way of collecting and organizing data in such a way
that we can perform operations on these data in an effective and sometimes efficient way. This
notion of data structures involves:

• a set of data elements;


• the way the data elements are logically related;
• and a set of allowable operations on the data elements.

From this notion, data structures are structures programmed to store ordered data, so that
various operations can be performed on it easily. It represents the knowledge of data to be
organized in memory, both primary and secondary. It should be designed and implemented in
such a way that it reduces the complexity and increases the efficiency.

Figure 1 Classification of Data Structures

Data type is another term usually used synonymously with data structures but in itself is
different from data structures. In programming, data type is a set of values from which a variable,
constant, function, or other expression may take its value. For example, integer takes on whole
numbers, real numbers represent fixed-point and floating-point values, character takes on
alphanumeric values, Boolean represents a true or false value, while date/time represents a range
of values depicting date and time. Data type can be thought of as a set of the “same kind” of data

2
processed by the computer. Data has to be examined carefully so that the most appropriate data
type can be used.

Basically, anything that can store data can be called as a data structure, hence Integer,
Float, Boolean, Char, etc., all are data structures. They are known as Primitive Data Structures
or Basic Type. Figure 1 and Table 1 represent two ways of looking at data structures. Though
differently labeled with varying categories or classifications, you can see that they show almost
the same thing.

Table 1 Categories of Data Structures

Complex Data Structure


Primitive Compound Data Structure
Simple Data
Data File
Structure Non-Linear
Structure Organization
Linear
Binary N-ary
Graph
General Sequential
Binary Tree
Integer Array List Tree Relative
Binary
Char String Stack M-way Indexed
Search Tree
Search Tree Sequential
Real Record Queue
AVL Tree
Heap Multi-Key
Boolean
B-Tree

Basic Data Type or Primitive Data Structures

Basic Data Type or Primitive Data Structures represent a set of individual data and is
frequently used to create a program. Sometimes called atomic data structure as they represent a
form where data can no longer be divided or have no parts. This group can be further divided into
Simple Type and Pointer Type.

Simple Type

Simple type is the most basic data type which is usually declared according to the
syntax rule of a programming language. Most common data types fall under this group.

• Integer type – represents integers or whole numbers where in the maximum or


minimum value is the unit of data that a computer can process at one time and is
determined by the length of one word.

• Real number type – represent fixed-point and floating-point numbers

• Character type – comprised of alphabets, numerals, and symbols as characters. A


character code is expressed as a binary number inside a computer.

• Logical type – sometimes referred to as Boolean type where the values are used
in performing logical operations such as AND, OR, and NOT.

3
• Enumeration type – a data type that enumerates all possible values of variables.

• Partial type – used to specify an original-value subset by constraining existing data


types, that is identifying upper and lower limits of a variable.

Pointer Type

Pointer type are addresses that are allocated in a main memory unit. Pointer types
are used to refer to variables, file records, or functions.

Pointer-type variable Variable “b”

(Address of
Data
variable “b”)

Figure 2 Pointer type

Structure Type or Simple Data Structure

Structure Type or Simple Data Structure is a data structure that contains a basic data type
or any of the defined data types as its elements. Arrays, strings, and records are examples of
Structure Type. They represent a collection of data elements. Array type or array is simply a finite
set of elements having the same type referenced under a common name. If this type is a character
type, we refer to it as a string or a collection of character elements. Record type on the other hand
is also a set of elements but this time of different data types referenced under a common name.
This is useful if we would like to organized a collection of data but do not want to manage them
separately (individually).

Abstract Data Type

Abstract Data Type is a part of Basic Data Structure but represents those under Problem-
oriented Data Structure. Abstract Data Type or ADT is almost always synonymous to Data
Structures but represents more of a logical description (abstract) rather than actual
implementation. ADT is basically a mathematical model where a set of data values and its
associated operations are precisely specified independent of any particular implementation.

Figure 3 Abstract Data Type representation

4
ADT is a kind of data abstraction where a type’s internal form is hidden (information hiding)
behind a set of access functions. By data abstraction, we mean any implementation of data in
which the implementation details are hidden (abstracted) from the user. Hiding data on the level
of data type is often called data encapsulation.

Think of ADT as a black box where inputs and outputs are known but how things are
programmed are not (ADTs hide implementation details). This idea keep internal calculations
private providing better modularity and allows for localize changes and better division of labor. A
data structure is the implementation of an ADT where the operations associated with the ADT are
implemented by one or more functions.

Logical and Physical Forms

Every data items have both a logical and physical form.

1. Logical form: definition of the data item within an ADT (e.g. integers in mathematical
sense: +,-, *, / (operations)
2. Physical form: implementation of the data item (e.g. 16 or 32 bit integers)

DATA TYPE

ADT: Data Items:


Type + Operations Logical Form

Data Structure: Data Items:


Storage Space + functions Physical Form

Figure 4 Logical and Physical Form of a Data Type

Defining an ADT depends on the application one is making and there are different
definitions for the same application. An ADT hides implementation details providing different
implementations for the same ADT. When the ADT is given, its data type can be used by the
programmer (e.g., string and math libraries in C). When the implementation changes, the
programs need not changed.

Classification of Data Structures based on Characteristics

The different data structures can also be classified on the basis of their characteristics.
The following table presents the different characteristics and how they are described.

5
Table 2 Classification of Data Structures based on Characteristics

Characteristic Description
Linear In linear data structures, the data items are arranged in a linear
sequence. Example: Array
Non-Linear For non-linear data structures, the data items are not in sequence.
Example: Tree, Graph
Homogeneous Homogeneous data structures represent a structure whose elements
are of the same type. Example: Array
Non-Homogeneous In Non-homogeneous data structure, the elements may or may not be
of the same type. Example: Structures
Static Static data structures are those whose sizes and structures associated
memory locations are fixed, at compile time. Example: Array
Dynamic Dynamic structures are those which expands or shrinks depending
upon the program need and its execution. Also, their associated
memory locations changes. Example: Linked List created using
pointers.

Types of data structures

Data structure types are determined by what types of operations are required or what
kinds of algorithms are going to be applied. These types include:

• Arrays - An array stores a collection of items at adjoining memory locations. Items that
are the same type get stored together so that the position of each element can be
calculated or retrieved easily. Arrays can be fixed or flexible in length.
• Stacks - A stack stores a collection of items in the linear order that operations are applied.
This order could be last in first out (LIFO) or first in first out (FIFO).
• Queues - A queue stores a collection of items similar to a stack; however, the operation
order can only be first in first out.
• Linked lists - A linked list stores a collection of items in a linear order. Each element, or
node, in a linked list contains a data item as well as a reference, or link, to the next item
in the list.
• Trees - A tree stores a collection of items in an abstract, hierarchical way. Each node is
linked to other nodes and can have multiple sub-values, also known as children.
• Graphs - A graph stores a collection of items in a non-linear fashion. Graphs are made
up of a finite set of nodes, also known as vertices, and lines that connect them, also known
as edges. These are useful for representing real-life systems such as computer networks.
• Tries - A trie, or keyword tree, is a data structure that stores strings as data items that can
be organized in a visual graph.
• Hash tables - A hash table, or a hash map, stores a collection of items in an associative
array that plots keys to values. A hash table uses a hash function to convert an index into
an array of buckets that contain the desired data item.

6
These are considered complex data structures as they can store large amounts of
interconnected data. Examples of primitive, or basic, data structures are integers, floats, Booleans
and characters.

Uses of data structures

In general, data structures are used to implement the physical forms of abstract data types.
This can be translated into a variety of applications, such as displaying a relational database as
a binary tree.

In programming languages, data structures are used to organize code and information in
a digital space. For example, Python lists and dictionaries or JavaScript array and objects are
common coding structures used for storing and retrieving information. Data structures are also a
crucial part of designing efficient software.

Importance of data structures

Data structures are essential for managing large amounts of data, such as information
kept in databases or indexing services, efficiently. Proper maintenance of data systems requires
the identification of memory allocation, data interrelationships and data processes, all of which
data structures help with.

Additionally, it is not only important to use data structures but it is important to choose the
proper data structure for each task. Choosing an ill-suited data structure could result in slow
runtimes or unresponsive code. A few factors to consider when picking a data structure include
what kind of information will be stored, where should existing data be placed, how should data be
sorted and how much memory should be reserved for the data.

Watch:

• What are Data Structures? By CS Dojo (https://youtu.be/bum_19loj9A)


• Data Structures By CrashCourse (https://youtu.be/DuDz6B4cqVc)
• Do You Need To Learn Data Structures and Algorithms? By Andy Sterkowitz
(https://youtu.be/2mOgMkSFLiA)

Review:

1. What is a data structure? How is it different from an abstract data type?


2. What is abstraction?
3. Name two things you can use data structures for.
4. Is our mental picture of a data structure always the same as the actual way the data
structure is represented in the computer?
5. When we consider a data structure from the abstract viewpoint, what are we mainly
interested in?

7
Unit 2 – Algorithm Analysis

Overview:
Algorithm Analysis or Analysis of Algorithm is the theoretical study of computer program’s
performance and resource usage. In this unit, we present the idea of algorithms and how
performance and resource usage affect the effectiveness of an algorithm.

Module Objectives:
At the end of this module, you should be able to:

• Discuss the concept of algorithms


• Discuss how to determine space complexity
• Discuss how to determine time complexity
• Know precise ways of analyzing whether a data structure or algorithm is good.

Course Materials:

An algorithm is a finite set of instructions or logic, written in order, to accomplish a certain


predefined task. Algorithm is not the complete code or program, it is just the core logic (solution)
of a problem, which can be expressed either as an informal high-level description as pseudocode
or using a flowchart.

Every algorithm must satisfy the following criteria:

1. Input – An algorithm has zero or more input quantities which are externally supplied taken
from a set of objects called the domain of the algorithm.

2. Output - It has one or more output quantities which generate a set called the range of the
algorithm.

3. Definiteness - Each instruction must be clear and unambiguous, meaning each step of
an algorithm must be precisely defined.

4. Finiteness - If we trace out the instructions of an algorithm, then for all cases the algorithm
will terminate after a finite number of steps.

5. Correctness or Effectiveness - Every instruction must be sufficiently basic that it can in


principle, be carried out by a person by manual means and must generate a correct output.

8
To give a concrete example on the concepts of algorithms, consider Euclid’s algorithm for
finding the greatest common divisor of two positive integers m and n. Euclid’s algorithm is:

Step 1: Divide m by n and let r be the remainder

Step 2: If r is zero, stop; the answer is n.

Step 3: Set m ß n, n ß r; got back to Step 1.

Let us try to gain more insight into it, by simply trying it out. So, let m = 608, n = 133. Then:
!"# '!
Loop 1: Step 1. = 4 , so r ß 76
%&& %&&

Step 2. r <> 0 so we continue

Step 3. m ß 133; n ß 76
%&& ('
Loop 2: Step 1. '!
= 1 '!
, so r ß 57

Step 2. r <> 0 so we continue

Step 3. m ß 76; n ß 57
'! %)
Loop 3: Step 1. ('
= 1 ('
, so r ß 19

Step 2. r <> 0 so we continue

Step 3. m ß 57; n ß 19
(' "
Loop 4: Step 1. = 3 , so r ß 0
%) %)

Step 2. r is equal 0, stop; gcd = 19

Let us now put to test Euclid’s method in terms of the five criteria of an algorithm.

1. Input. The input consists of m and n, which are taken from the set of positive integers.

2. Output. The output is n, which contains the greatest common divisor upon termination of
the algorithm.

3. Definiteness. The stipulation that m and n be positive integers precisely defines step 1,
since there is no ambiguity as to what the remainder is upon dividing a positive integer by
another. If m and n were any real number, then step 1 may not satisfy the criterion of
definiteness.

4. Finiteness. Will the algorithm terminate for any input pair of positive integers m and n?
The answer is yes, it will. In step 1, r is always less than n. This is the value assigned to
n in step 3, unless r is already zero, in which case the algorithm terminates in step 2.

9
Therefore, each time step 1 is subsequently executed, the value of n is smaller than the
previous. A decreasing sequence of positive integers eventually terminates (at worst, at
n=1), so the algorithm must also terminate.

5. Correctness or Effectiveness. We have been able to carry out the steps of the algorithm
in working out the example. If we are patient enough, we should be able to do the same
for any pair (m, n), even if it takes a thousand loops.

Effectiveness of Programs

Effectiveness is defined as being able to perform an action or make a result (output) based
on specifications or what is meant to be achieved. For a program (or an algorithm for that matter)
to be considered effective it must answer two factors.

1. Does it run correctly? Provides no error and runs in accordance with the specifications
given.
2. Does it run efficiently? The program runs effectively within the shortest time possible.

Analysis of Algorithms

The effectiveness of an algorithm can be seen through its performance. Program


performance refers to a characteristic of the program as a whole, while leaving the term efficiency
to refer to a characteristic of a part of the program.

An algorithm is said to be efficient and fast, if it takes less time to execute and consumes
less memory space. Algorithms that are equally correct can vary in their utilization of
computational resources. The performance of an algorithm or algorithm efficiency is usually
measured on the basis of the following properties:

1. Space complexity. Space complexity refers to the amount of memory space required
by the algorithm, during the course of its execution. Space complexity must be taken
seriously for multi-user systems and in situations where limited memory is available.

An algorithm generally requires space for following components:

a. Instruction Space: The space required to store the executable version of the
program. This space is fixed but varies depending upon the number of lines of
code in the program.

b. Data Space: Is the space required to store all the constants and variables
(including temporary variables) value.

c. Environment Space: Refers to the space required to store the environment


information needed to resume the suspended function. An algorithm (function)
may be called inside another algorithm (function). In such a situation, the
current variables are pushed onto the system stack, where they wait for further
execution and then the call to the inside algorithm (function) is made.

10
For example, if a function A() calls function B() inside it, then all the variables
of the function A() will get stored on the system stack temporarily, while the
function B() is called and executed inside the function A().

When memory was expensive, we focused on making programs as space efficient as


possible and developed schemes to make memory appear larger than it really was
(virtual memory and memory paging schemes)

Space complexity is still important in the field of embedded computing especially for
handheld computer-based equipment like cell phones, palm devices, etc.

2. Time complexity. Time Complexity is a way to represent the amount of time required
by the program to run till its completion. It's generally a good practice to try to keep the
time required minimum, so that our algorithm completes its execution in the minimum
time possible.

Note however that time complexity is affected by the complexity of instruction which
depends on the following:
a. machine execution time
b. time to execute the instruction
c. the instruction set
d. translation (the use of compilers)

All of which pertains to the physical hardware that runs the program and the software
tools that is used to translate the program into machine readable form.

Space Complexity of Algorithms

When a solution to a problem is written some memory is required to complete. For any
algorithm memory may be used for the following:

1. Variables (this include the constant values and temporary values)


2. Program instruction
3. Execution

As defined earlier, space complexity is the amount of memory used by the algorithm
(including input values to the algorithm) to execute and produce result. Sometime Auxiliary Space
is confused with Space Complexity. But Auxiliary Space is the extra space or the temporary space
used by the algorithm during its execution. Thus, we can say:

Space Complexity = Auxiliary Space + Input space

Also earlier, while executing, an algorithm uses or require memory space for three
reasons: 1) instruction space; 2) environment space or environmental stack; and 3) data space.
But in calculating the Space Complexity of any algorithm, we usually consider only Data Space
and neglect Instruction Space and Environmental Stack. The two being affected by the type of
hardware used to run the algorithm.

11
For calculating the space complexity, we need to know the value of memory used by
different type of datatype variables, which generally varies for different operating systems, but the
method for calculating the space complexity remains the same.

Table 3 Size in bytes of Different Data Types

Type Size
bool, char, unsigned char, signed char, __int8 1 byte
__int16, short, unsigned short, wchar_t, __wchar_t 2 bytes
float, __int32, int, unsigned int, long, unsigned long 4 bytes
double, __int64, long double, long long 8 bytes

Now let's learn how to compute space complexity by taking a few examples:

{
int z = a + b + c;
return(z);
}

In the above code, variables a, b, c and z are all integer types, hence they will take up 4
bytes each, so total memory requirement will be (4(4) + 4) = 20 bytes, the additional 4 bytes is for
return value. And because this space requirement is fixed for the above example, hence it is
called Constant Space Complexity.

Let's have another example, this time a bit complex one,

// n is the length of array a[]


int sum(int a[], int n)
{
int x = 0; // 4 bytes for x
for(int i = 0; i < n; i++) // 4 bytes for i
{
x = x + a[i];
}
return(x);
}

In the above code, 4*n bytes of space is required for the array a[] elements. 4 bytes each
for x, n, i and the return value.

Hence the total memory requirement will be (4n + 16), which is increasing linearly with the
increase in the input value n, hence it is called as Linear Space Complexity. Similarly, we can
have quadratic and other complex space complexity as well, as the complexity of an algorithm
increases. But we should always focus on writing algorithm code in such a way that we keep the
space complexity minimum.

12
Time Complexity of Algorithms

In order to analyze an algorithm, we need to determine its (algorithm) efficiency which


measures the amount of resources consumed in solving a problem of size in in terms of time and
space. A way of doing this is by benchmarking which is implementing the algorithm, running using
some specific input and measure time taken. But this method is better for comparing performance
of processors rather than for comparing performance of algorithms.

The best option is to perform asymptotic analysis where we evaluate the performance of
an algorithm in terms of the input size (problem size) in relation with the processing time (or space)
required to solve the problem. In here we look for three cases to examine:

1. Best Case – if the algorithm is executed, the fewest number of instructions are
executed.
2. Average Case – executing the algorithm produces path lengths that will on average
be the same.
3. Worst Case – executing the algorithm produces path lengths that are always a
maximum.

Of the three cases, the only useful case from the standpoint of program design is that of
the worst case. Worst case helps us answer the software life cycle question of: If it is good enough
today, will it be good enough tomorrow?

For any defined problem, there can be N number of solutions. This is true in general. If I
have a problem and I discuss about the problem with all of my friends, they will all suggest me
different solutions. And I am the one who has to decide which solution is the best based on the
circumstances.

Similarly, for any problem which must be solved using a program, there can be infinite
number of solutions. Let's take a simple example to understand this. Below we have two different
algorithms to find square of a number (for some time, forget that square of any number n is n*n):

One solution to this problem can be running a loop for n times, starting with the number n
and adding n to it every time.

/*
we have to calculate the square of n
*/
for i=1 to n
do n = n + n
// when the loop ends n will hold its square
return n

Or, we can simply use a mathematical operator * (multiplication) to find the square.

/*
we have to calculate the square of n
*/
return n*n

13
In the above two simple algorithms, we saw how a single problem can have many
solutions. While the first solution required a loop, which will execute for n number of times, the
second solution used a mathematical operator * to return the result in one line. So which one is
the better approach, of course the second one.

Time complexity of an algorithm signifies the total time required by the program to run till
its completion. The time complexity of algorithms is most commonly expressed using the big O
notation which is an asymptotic notation to represent the time complexity.

Time Complexity is most commonly estimated by counting the number of elementary steps
(we call this frequency count) performed by any algorithm to finish execution. Like in the example
above, for the first code the loop will run n number of times, so the time complexity will be n at
least and as the value of n will increase the time taken will also increase. While for the second
code, time complexity is constant, because it will never be dependent on the value of n, it will
always give the result in 1 step. And since the algorithm's performance may vary with different
types of input data, hence for an algorithm we usually use the worst-case Time complexity of an
algorithm because that is the maximum time taken for any input size.

Let us now tap onto the next big topic related to Time complexity, which is How to
Calculate Time Complexity. It becomes very confusing sometimes, but we will try to explain it in
the simplest way.

Now the most common metric for calculating time complexity is Big O notation. This
removes all constant factors so that the running time can be estimated in relation to N, as N
approaches infinity. In general you can think of it like this:

statement;

Above we have a single statement. Its Time Complexity will be constant. The running time
of the statement will not change in relation to N.

for(i=0; i < N; i++)


{
statement;
}

The time complexity for the above algorithm will be Linear. The running time of the loop is
directly proportional to N. When N doubles, so does the running time.

This time, the time complexity for the code below will be Quadratic. The running time of
the two loops is proportional to the square of N. When N doubles, the running time increases by
N * N.

for(i=0; i < N; i++)


{
for(j=0; j < N;j++)
{
statement;
}
}

14
The algorithm below breaks a set of numbers into halves to search a particular field. Now,
this algorithm will have a Logarithmic Time Complexity. The running time of the algorithm is
proportional to the number of times N can be divided by 2 (N is high-low here). This is because
the algorithm divides the working area in half with each iteration.

while (low <= high)


{
mid = (low + high) / 2;
if (target < list[mid])
high = mid - 1;
else if (target > list[mid])
low = mid + 1;
else break;
}

Taking the previous algorithm forward, below we have a small logic of Quick Sort. In Quick
Sort, we divide the list into halves every time, but we repeat the iteration N times (where N is the
size of list). Hence time complexity will be N*log(N). The running time consists of N loops (iterative
or recursive) that are logarithmic, thus the algorithm is a combination of linear and logarithmic.

void quicksort(int list[], int left, int right)


{
int pivot = partition(list, left, right);
quicksort(list, left, pivot - 1);
quicksort(list, pivot + 1, right);
}

In general, doing something with every item in one dimension is linear, doing something
with every item in two dimensions is quadratic, and dividing the working area in half is logarithmic.

Figure 5 Comparing Growth Rate

15
Types of Notations for Time Complexity

1. Big Oh denotes "fewer than or the same as" <expression> iterations.


2. Big Omega denotes "more than or the same as" <expression> iterations.
3. Big Theta denotes "the same as" <expression> iterations.
4. Little Oh denotes "fewer than" <expression> iterations.
5. Little Omega denotes "more than" <expression> iterations.

O(expression) is the set of functions that grow slower than or at the same rate as
expression. It indicates the maximum required by an algorithm for all input values. It represents
the worst case of an algorithm's time complexity.

Omega(expression) is the set of functions that grow faster than or at the same rate as
expression. It indicates the minimum time required by an algorithm for all input values. It
represents the best case of an algorithm's time complexity.

Theta(expression) consist of all the functions that lie in both O(expression) and
Omega(expression). It indicates the average bound of an algorithm and it represents the average
case of an algorithm's time complexity.

Running Time Calculations

Generally, there are several algorithmic solutions to every programming problem and we
would like to eliminate the bad ones early on so an analysis is usually required. The ability to do
an analysis usually provides insight into designing efficient algorithms. The analysis also generally
pinpoints the bottlenecks, which are worth coding carefully.

To simplify the analysis, we adopt the convention that there are no particular units of time
thus throwing away leading constants. We will also throw away low-order terms, so what we are
essentially doing is computing a Big-Oh running time. Since Big-Oh is an upper bound, we must
be careful never to underestimate the running time of the program. In effect, the answer provided
is a guarantee that the program will terminate within a certain time period. The program may stop
earlier than this, but never later.

Example.

Here is a simple program fragment to calculate ∑*


+,% 𝑖
&

int sum( int n )


{
int partialSum;

1 partialSum = 0;
2 for( int i = 1; i <= n; i++ )
3 partialSum += i * i * i;
4 return partialSum;
}

16
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 method and returning, for a total of 6N + 4. Thus, we say that this method is O(N).

If we had to perform all this work every time we needed to analyze a program, the task would
quickly become infeasible. Fortunately, since we are giving the answer in terms of Big-Oh, there
are lots of shortcuts that can be taken without affecting the final answer. For instance, line 3 is
obviously an O(1) statement (per execution), so it is silly to count precisely whether it is two, three,
or four units; it does not matter. Line 1 is obviously insignificant compared with the for loop, so it
is silly to waste time here. This leads to several general rules.

General Rules for Running Time Calculations

Rule 1—FOR loops

The running time of a for loop is at most the running time of the statements inside the for
loop (including tests) times the number of iterations.

Rule 2—Nested loops

Analyze these inside out. The total running time of a statement inside a group of nested
loops is the running time of the statement multiplied by the product of the sizes of all the
loops.

As an example, the following program fragment is O(N2):

for (i=0; i<n; ++i)


for (j=0; j<n; ++j)
++k;

Rule 3—Consecutive Statements

These just add (which means that the maximum is the one that counts). Example is the
following program fragment, which has O(N) work followed by O(N2) work, is also O(N2):

for (i=0; i<n; ++i)


a[i] = 0;
for (i=0; i<n; ++i)
for (j=0; j<n; ++j)
a[i] += a[j] + i + j

17
Rule 4—If/Else

For the fragment

if (condition)
S1
else
S2

the running time of an if/else statement is never more than the running time of the test
plus the larger of the running times of S1 and S2.

Other rules are obvious, but a basic strategy of analyzing from the inside (or deepest part)
out works. If there are function calls, these must be analyzed first. If there are recursive functions,
there are several options. If the recursion is really just a thinly veiled for loop, the analysis is
usually trivial.

Watch:

• What’s an algorithm? By TED-Ed (https://youtu.be/6hfOvs8pY1k)


• Introduction to Big O Notation and Time Complexity By CS Dojo
(https://youtu.be/D6xkbGLQesk)

Read:

• Section 1.4 – Analysis of Algorithms (pp. 172-205)


Sedgewick, Robert, Wayne, Kevin (2011). Algorithms 4th Edition

• Chapter 4 – Algorithm Analysis


Goodrich, Michael T. (2014). Data Structures and Algorithms in Java 6th Edition

• Chapter 2 – Algorithm Analysis


Weiss, Mark Allen (2014). Data Structures And Algorithm Analysis In C++ 4th Edition

Review:

1. What is an algorithm?
2. In formal computer science, how do you distinguish between algorithm and a program?
3. Why is there a need to analyze algorithms?

18
Activities/Assessments:
1. For each of the following three program fragments:
a. Give an analysis of the running time (Big-Oh will do).
b. Implement the code in the language of your choice, and give the running time for
several values of N.
c. Compare your analysis with the actual running times.

(1) sum = 0;
for( i = 0; i < n; ++i )
++sum;

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

(3) sum = 0;
for( i = 0; i < n; ++i )
for( j = 0; j < i * i; ++j )
for( k = 0; k < j; ++k )
++sum;

19
Unit 3 – Arrays

Overview:
Most programming languages in one way or another have its own implementation of an array. In
this unit we present an array which is used by most of the data structures to implement their
algorithms.

Module Objectives:
After successful Completion of this module, you should be able to:

• Define and describe array and its characteristics


• Discuss the different ways to represent an array
• Discuss the basic operations in an array
• Develop your own implementation of an array
• Apply array implementation in solving programming challenges

Course Materials:

Array type or array is defined as a finite set of elements having the same type referenced
under a common name. Each individual data is called an array element and must be of the same
type and size. Figure 6 shows a one-dimensional array where data is arrayed in a line. Each data
element is accessed using an index.

Figure 6 One-dimensional array

Arrays can have more than one dimension, these are called multidimensional arrays.
Though they may have certain limitations as to the number of definable dimensions, depending
on the type of programming language or compiler used. Figure 7 shows a two-dimensional array
sometimes called a table as it represents a series of rows and columns. The figure shows data
lined up in both vertical and horizontal directions. Data elements are identified by a row index and
a column index. Two-dimensional arrays are also called array of arrays.

20
Figure 7 Two-dimensional array

The figure below represents a three-dimensional array. Notice that aside from the rows
and columns, a third element representing depth can also be seen.

Figure 8 Three-dimensional Array

Memory Address Calculation in an Array

An array is a collection of items stored at contiguous memory locations. The idea is to


store multiple items of the same type together thus arrays are classified as homogeneous data
structures because they store elements of the same type. This also makes it easier to calculate
the position of each element by simply adding an offset to a base value (the base value here is
the memory location of the first element of the array denoted by the name of the array).

Figure 9 Array Elements in Contiguous Memory Locations

21
The above image can be looked as a top-level view of a staircase where you are at the
base of the staircase (index 0). Each element can be uniquely identified by their index in the array
(in a similar way as you could identify your friends by the step on which they were on in the above
example).

Address Calculation in Single (One) Dimension Array

Figure 10 Address Calculation in Single Dimension Array

In doing address calculation of an array element it is important to take note of the


base address and the storage size of one element (in this case the size of the data type).
The address of an element of an array say “A[X]” is calculated using the following formula:

Address of A[X] = B + W * (X – LB)

Where:
B = base address
W = storage size of one element stored in the array (in bytes)
X = subscript of element whose address is to be found
LB = Lower limit/Lower bound of subscript, if not specified assume 0 (zero)

Example:

Given the base address of an array B[1300…..1900] as 1020 and size of each element is
2 bytes in the memory. Find the address of B[1700].

Solution: The given values are: B = 1020, LB = 1300, W = 2, X = 1700

Address of A [ X ] = B + W * ( X – LB )

= 1020 + 2 * (1700 – 1300)


= 1020 + 2 * 400
= 1020 + 800
= 1820 [Ans]

22
Address Calculation in Two Dimension Array

While storing the elements of a Two-dimensional array in memory, these are


allocated contiguous memory locations. Therefore, a two-dimension array must be
linearized so as to enable their storage. There are two alternatives to achieve linearization:
Row-Major and Column-Major.

Figure 11 Two-dimensional Array

Figure 12 Alternatives to Linearization of Two-dimension Array

Address of an element of any array say “A[ I ][ J ]” is calculated in two forms as


given: 1) Row Major System and 2) Column Major System.

Row Major System: The address of a location in Row Major System is calculated using
the following formula:

Address of A [ I ][ J ] = B + W * [ N * ( I – Lr ) + ( J – Lc ) ]

23
Column Major System: The address of a location in Column Major System is calculated
using the following formula:

Address of A [ I ][ J ] Column Major Wise = B + W * [( I – Lr ) + M * ( J – Lc )]

Where:
B = Base address
I = Row subscript of element whose address is to be found
J = Column subscript of element whose address is to be found
W = Storage Size of one element stored in the array (in byte)
Lr = Lower limit of row/start row index of matrix, if not given assume 0 (zero)
Lc = Lower limit of column/start column index of matrix, if not given assume 0
M = Number of rows of the given matrix
N = Number of columns of the given matrix

Note however that number of rows and columns of a matrix are usually given (like
A[20][30] or A[40][60] ) but if it is given as A[Lr- – – – – Ur, Lc- – – – – Uc]. In this case
number of rows and columns are calculated using the following methods:

Number of rows (M) will be calculated as = (Ur – Lr) + 1

Number of columns (N) will be calculated as = (Uc – Lc) + 1

And rest of the process will remain same as per requirement (Row Major Wise or
Column Major Wise).

Example:

An array X [-15……….10, 15……………40] requires one byte of storage. If beginning


location is 1500 determine the location of X [15][20].

Solution:

As you see here the number of rows and columns are not given in the question. So they
are calculated as:

Number or rows say M = (Ur – Lr) + 1 = [10 – (- 15)] +1 = 26


Number or columns say N = (Uc – Lc) + 1 = [40 – 15)] +1 = 26

(i) Column Major Wise Calculation of above equation

The given values are: B = 1500, W = 1 byte, I = 15, J = 20, Lr = -15, Lc = 15, M = 26

Address of A [ I ][ J ] = B + W * [ ( I – Lr ) + M * ( J – Lc ) ]

= 1500 + 1 * [(15 – (-15)) + 26 * (20 – 15)]


= 1500 + 1 * [30 + 26 * 5]
= 1500 + 1 * [160]
= 1660 [Ans]

24
(ii) Row Major Wise Calculation of above equation

The given values are: B = 1500, W = 1 byte, I = 15, J = 20, Lr = -15, Lc = 15, N = 26

Address of A [ I ][ J ] = B + W * [ N * ( I – Lr ) + ( J – Lc ) ]

= 1500 + 1* [26 * (15 – (-15))) + (20 – 15)]


= 1500 + 1 * [26 * 30 + 5]
= 1500 + 1 * [780 + 5]
= 1500 + 785
= 2285 [Ans]

Array Representation

Arrays can be declared in various ways in different programming languages.

Figure 13 Declaring an Array in C and Python

For illustration, let’s take C array declaration.

Figure 14 Array Representation in C

25
As seen in the illustration (Figure 14), following are the important points to be considered.

• Index starts with 0.


• Array length is 10 which means it can store 10 elements.
• Each element can be accessed via its index. For example, we can fetch an element at
index 6 as 19.

In C, when an array is initialized with size, then it assigns default values to its elements:

Table 4 Default values for an Array

Data Type Default Value


bool false
char 0
int 0
float 0.0
double 0.0f
void
wchar_t 0

Basic Array Operations

There are several basic operations supported by an array though the implementation
differs from one programming language to another. In Python, there is an array module that has
separate functions for performing array operations. In C language, the operations are explicitly
defined using loops and assignment statements. Following are the basic operations supported by
an array.

1. Traverse. This operation traverses through the elements of an array meaning we print
all the array elements one by one as we visit them.

2. Insertion. Insert operation is to insert one or more data elements into an array. Based
on the requirement, a new element can be added at the beginning, end, or any given
index of the array.

3. Deletion. Deletion refers to removing an existing element from the array and re-
organizing all elements of an array.

4. Search. With this operation, you can search for an item in an array based on a given
value (or through an index).

5. Update. Update operation refers to updating an existing element from the array at a
given index. This operation is quite similar to the insert method, except that it will
replace the existing value at the given index.

26
Table 5 Time Complexity of an Array

Operation Average Case Worst Case


Read/Update O(1) O(1)
Insert O(n) O(n)
Delete O(n) O(n)
Search O(n) O(n)
Traverse O(n) O(n)

Advantages and Disadvantages of Arrays

Advantages

1. Reading an array element is simple and efficient. As shown in the above table, the read
time of array is O(1) in both best and worst cases. This is because any element can be
instantly read using indexes (base address calculation behind the scene) without
traversing the whole array.

2. Array is a foundation of other data structures. For example, other data structures such as
LinkedList, Stack, Queue etc. are implemented using array.

3. All the elements of an array can be accessed using a single name (array name) along with
the index, which is readable, user-friendly and efficient rather than storing those elements
in different variables.

Disadvantages

1. While using array, we must need to make the decision of the size of the array in the
beginning, so if we are not aware how many elements we are going to store in array, it
would make the task difficult.

2. The size of the array is fixed so if at later point, if we need to store more elements in it
then it can’t be done. On the other hand, if we store a smaller number of elements than
the declared size, the remaining allocated memory is wasted.

Watch:

• An Overview of Arrays and Memory By CS Dojo (https://youtu.be/pmN9ExDf3yQ)

Read:

• Section 3.1 – Using Arrays (pp. 104-121)


Goodrich, Michael T. (2014). Data Structures and Algorithms in Java 6th Edition

27
• Arrays in C (https://www.w3resource.com/c-programming/c-array.php)
• Arrays (https://en.wikibooks.org/wiki/Data_Structures/Arrays)

Review:

1. What is an array?
2. What are sequential lists and how are they limited?
3. A one-dimensional array is a collection of what? A two-dimensional array is a collection of
what?
4. If an array holds integers, each of which is four bytes, how many bytes from the base
location of the array is the location of the fifth element?
5. Describe a row major ordering and a column major ordering.
6. On average, how many items must be moved to insert a new item into an unsorted array
with N items?
7. On average, how many items must be moved to delete an item from an unsorted array
with N items?
8. On average, how many items must be examined to find a particular item in an unsorted
array with N items?

Activities/Assessments:
1. How many values can be held by an array with dimensions?

a) X [0..8]
b) a [0..n]
c) b [-1..n, 1..m]
d) c [-n..0, 1..2]

2. When storing a two-dimensional array A with ten rows and ten columns in continuous
memory space in the direction of rows, what is the address where A[5,6] is stored? In this
question, the address is represented in decimal numbers.

Address
100 A[1,1]
101
102 A[1,2]
103
104

3. Assume that the following lists of numbers represents the physical view of a two-
dimensional array in memory. If the array has 3 columns and 3 rows, show the row major
ordering and the column major ordering for each list.

a) 4, 0, 1, 3, 5, 1, 7, 4, 4
b) 7, 7, 7, 3, 3, 3, 1, 1, 1
c) 1, 2, 3, 4, 5, 6, 7, 8, 9

28
4. Answer the following problems given the following array variable/s assuming that an int is
equal to 2 bytes. int x [4][3] = {5, 2, 9, 0, 3, 1, 4, 12, 6, 8, 10, 11};

a) what will be the total size (in bytes) of the array? ___________
b) what value is found at x [0][0]? _____________
c) what value is found at x [3][2]? _____________
d) what value is found at x [2][1]? _____________
e) what value is found at x [1][2]? _____________

Programming:

1. Write a program in C to find the maximum and minimum element in an array.


Test Data :
Input the number of elements to be stored in the array : 3
Input 3 elements in the array :
element - 0 : 45
element - 1 : 25
element - 2 : 21
Expected Output :
Maximum element is : 45
Minimum element is : 21

2. Write a program in C to sort elements of array in ascending order.


Test Data :
Input the size of array : 5
Input 5 elements in the array :
element - 0 : 2
element - 1 : 7
element - 2 : 4
element - 3 : 5
element - 4 : 9
Expected Output :
Elements of array in sorted ascending order:
24579

3. Write a program in C to delete an element at desired position from an array.


Test Data :
Input the size of array : 5
Input 5 elements in the array in ascending order:
element - 0 : 1
element - 1 : 2
element - 2 : 3
element - 3 : 4
element - 4 : 5
Input the position where to delete: 3
Expected Output :
The new list is : 1 2 4 5

29
Unit 4 – Linked Lists

Overview:
List provide a convenient way for keeping track of things in our everyday life: shopping lists,
laundry lists, list of thigs to be done, and so on. Similarly, lists provide a convenient way for
representing arbitrary nonnumeric information in a computer. The remainder of this unit will deal
with the list a formal data structure and will explain how it is represented and manipulated in a
computer.

Module Objectives:
After successful Completion of this module, you should be able to:

• Define and describe a linked list and its characteristics


• Discuss the different operations on a linked list
• Develop your own implementation of a linked list
• Apply your linked list implementation in solving programming challenges

Course Materials:

List is a finite, ordered collection of items (elements) of some element type E. Note that
this doesn't mean that the objects are in sorted order, it just means that each object has a position
in the List, starting with position zero (first element in the list), second element, and so on. In
programming list is describe as a collection of elements that have a linear relationship with each
other. A linear relationship means that, except for the first one, each element on the list has a
unique successor. Also lists have a property intuitively called size, which is simply the number of
elements on the list. Know that every list has a size.

In computer programs, lists are extremely versatile ADTs that provide storage for
information without imposing any limitations on how that information is added, accessed, or
removed. Recall that when we think about an ADT, we think about both the external and internal
views. The external view includes the "conceptual picture" and the set of "conceptual operations".
The conceptual picture of a List is something like this:

item 0
item 1
item 2
...
item n

30
and one reasonable set of operations is:

Operation Description
void add(E item) add item to the end of the List
void add(int pos, E item) add item at position pos in the List, moving the items originally
in positions pos through size()-1 one place to the right to make
room (error if pos is less than 0 or greater than size())
boolean contains(E item) return true iff item is in the List (i.e., there is an item x in the List
such that x.equals(item))
int size() return the number of items in the List
boolean isEmpty() return true iff the List is empty
E get(int pos) return the item at position pos in the List (error if pos is less than
0 or greater than or equal to size())
E remove(int pos) remove and return the item at position pos in the List, moving
the items originally in positions pos+1 through size()-1 one
place to the left to fill in the gap (error if pos is less than 0 or
greater than or equal to size())

Many other operations are possible; when designing an ADT, you should try to provide
enough operations to make the ADT useful in many contexts, but not so many that it gets
confusing. It is not always easy to achieve this goal; it will sometimes be necessary to add
operations in order for a new application to use an existing ADT.

List Implementations

In some ways, a List is similar to an array: both Lists and arrays are ordered collections of
data elements and in both cases you can add or access items at a particular position (and in both
cases we consider the first position to be position zero). You can also find out how many items
are in a List and how large an array is.

You can actually implement a list using an array which is one of the most common
implementation of a list. Unfortunately, one disadvantage of using arrays to store data is that
arrays are static structures and therefore cannot be easily extended or reduced to fit the data set.
Arrays are also expensive to maintain new insertions and deletions.

A solution is to use another implementation, that of using pointers or memory cells forming
what we call a Linked list. A linked list is a linear data structure where each element is a separate
object connected together via links.

The implementation of a linked list in C is done using pointers. Linked list can be visualized
as a chain of nodes, where every node points to the next node. Each element (node) of a list is
comprised of two items - the data and a reference to the next node. The last node has a reference
to null and often identified as tail. The entry point into a linked list is called the head of the list. It

31
should be noted that head is not a separate node, but the reference to the first node. If the list is
empty then the head is a null reference.

Figure 15 Linked List

Implementing a list in C language requires the use of structures and pointers. Each
structures represents a node having some data and also a pointer to another structure of the
same kind. This pointer holds the address of the next node and creates a link between two nodes.
The following code shows how to define a node in C (this is for a singly linked list).

struct node
{
int data;
struct node *next;
};

The first data member of the structure (named node) is an integer to hold an integer and
the second data member is the pointer to a node (same structure). This means that the second
data member holds the address of the next node and in this way, every node is connected as
represented in the picture above.

A linked list is a dynamic data structure. Unlike an array, a linked list does not store its
nodes in consecutive memory locations. The number of nodes in a list is not fixed and can grow
and shrink on demand. Any application which has to deal with an unknown number of objects will
need to use a linked list.

Another point of difference between an array and a linked list is that a linked list does not
allow random access of data or direct access to the individual elements. If you want to access a
particular item then you have to start at the head and follow the references until you get to that
item. Nodes in a linked list can be accessed only in a sequential manner. But like an array,
insertions and deletions can be done at any point in the list in a constant time. A disadvantage to
a linked list is that it uses more memory compare with an array - we need an extra 4 bytes (on
32-bit CPU) just to store a reference to the next node.

Types of Linked List

Following are the various types of linked list.

1. Simple Linked List or Singly Linked List – navigation in the list is forward only. In this
type of linked list, every node stores address or reference of next node in list and the
last node has next address or reference as NULL.

32
Figure 16 Singly Linked List

2. Doubly Linked List – navigation through the list can be both ways – forward and
backward. In this type of Linked list, there are two references associated with each
node, One of the reference points to the next node and one to the previous node.
Advantage of this data structure is that we can traverse in both the directions and for
deletion we don’t need to have explicit access to previous node.

Figure 17 Doubly Linked List

3. Circular Linked List – The last item (TAIL) in the list contains a link to the first element
(HEAD) as next and the first element has a link to the last element as previous. Circular
linked list is a linked list where all nodes are connected to form a circle. There is no
NULL at the end. A circular linked list can be a singly circular linked list or doubly
circular linked list. Advantage of this data structure is that any node can be made as
starting node. This is useful in implementation of circular queue in linked list.

Figure 18 Circular Singly Linked List

Figure 19 Circular Doubly Linked List

Basic Operations

Though there exist various types of linked list, some of the basic operations supported by
a linked list are almost the same with slight modification. The operations to be presented below
are that of a singly linked list.

33
• Addition/insertion – adding an element in a list. Note that the process of adding or
inserting an item varies depending on where the new element will be placed (at the
beginning, at the end of the list, or in between nodes)

• Deletion – removing an item from the list.

• Search – finding an element from the list. Searching for an item in the list always begins
at the head.

Addition/Insertion Operation

The process of adding or inserting a new node to a linked list (singly linked list)
depends on the position where the new item is to be placed. Following are the processes
for adding an item to a list assuming the list already has data on it.

1. Adding a node at the beginning (prepend)

Step 1 – Make a new node


Step 2 – Point the ‘next’ of the new node to the ‘head’ of the linked list.
Step 3 – Mark the new node as ‘head’.

Figure 20 Adding a node at the beginning of a list

2. Adding a node at the end (append)

Step 1 – Make a new node


Step 2 – Point the last node of the linked list to the new node.

Figure 21 Adding a node at the end of a list

3. Adding a node in between nodes (insert after prev)

Step 1 – Make a new node


Step 2 – Point the ‘next’ of the new node to the node ‘cur’. Till now, two nodes are
pointing at the same node ‘cur’, the node ‘prev’ and the new node.

34
Step 3 – Point the ‘next’ of ‘prev’ to the new node.

Figure 22 Adding a node in between existing node

Deletion Operation

We delete any node of a linked list by connecting the predecessor node of the
node to be deleted by the successor node of the same node. For example, if we have a
linked list a → b → c, then to delete the node ‘b’, we will connect ‘a’ to ‘c’ i.e., a → c. But
this will make the node ‘b’ inaccessible and this type of inaccessible nodes are called
garbage and we need to clean this garbage by using the ‘free’ function in C.

Step 1 – Find a node to be deleted (in this case ‘cur’)


Step 2 – Point the ‘next’ of ‘prev’ to the node pointed to by ‘next’ of ‘cur’. At this point,
two nodes are pointing at the same node (successor of ‘cur’).
Step 3 – node ‘cur’ is no longer part of the list thus we need to free up the space using
the ‘free’ function in C.

Figure 23 Deleting a node from a linked list

Search Operation

Searching for an item in the list start with the head and accessing each node
comparing the value of the item being searched until we reach null (the end of the list). Do
not change the head reference. The same process (without the comparison) is done when
traversing all entries in the list.

Figure 24 Traversing a linked list

35
Watch:

• Pointers By CS50 (https://youtu.be/XISnO2YhnsY)


• Introduction to Linked Lists By CS Dojo (https://youtu.be/WwfhLC16bis)
• Singly-Linked Lists By CS50 (https://youtu.be/zQI3FyWm144)
• Doubly-Linked Lists By CS50 (https://youtu.be/FHMPswJDCvU)

Read:

• Section 3.2 – The List ADT (pp. 78-80)


Weiss, Mark Allen (2014). Data Structures And Algorithm Analysis In C++ 4th Edition

• Section 3.2 – Singly Linked Lists (pp. 122-127)


Goodrich, Michael T. (2014). Data Structures and Algorithms in Java 6th Edition

• Section 3.3 – Circularly Linked Lists (pp. 128-131)


Goodrich, Michael T. (2014). Data Structures and Algorithms in Java 6th Edition

• Section 3.4 – Doubly Linked Lists (pp. 132-137)


Goodrich, Michael T. (2014). Data Structures and Algorithms in Java 6th Edition

• Section 3.5 – Equivalence Testing (pp. 138-140)


Goodrich, Michael T. (2014). Data Structures and Algorithms in Java 6th Edition

Review:

1. What is a list?
2. What is the different between a list and linked list?
3. How is an array different from a linked list?
4. What are the different types of linked list?

Activities/Assessments:
1. Fill in the values indicated by the pointer variable expressions below, based on the
following linked list:

a) A->next->next->info ___________________
b) B->next->info ___________________
c) B->next->back->back->info ___________________
d) A->back->next->info ________________

36
2. The figure below is a uni-directional list. Manila is the head of the list and the pointer
contains addresses of data shown below. Batanes is the tail of the list and the pointer
contains 0. Choose one correct way to insert Cebu placed at address 150 between
Boracay and Davao.

Pointer for the head Address Data Pointer


10 10 Manila 50
30 Batanes 0
50 Vigan 90
70 Davao 30
90 Boracay 70
150 Cebu

a) The pointer for Cebu to be set to 50 and that for Davao to be set to 150
b) The pointer for Cebu to be set to 70 and that for Boracay to be set to 150
c) The pointer for Cebu to be set to 90 and that for Davao to be set to 150
d) The pointer for Cebu to be set to 150 and that for Boracay to be set to 90

3. Show what is written by the following segments of code:


a) P = (node) malloc(sizeof(NodeEntry));
Q = (node) malloc(sizeof(NodeEntry));
P->info = 5; Q->info = 6;
P = Q;
P->info = 1;
printf(“%d %d”, P->info, Q->info);

b) P = (node) malloc(sizeof(NodeEntry));
P->info = 3;
Q = (node) malloc(sizeof(NodeEntry));
Q->info = 2;
P = (node) malloc(sizeof(NodeEntry));
P->info = Q->info;
Q->info = 0;
printf(“%d %d”, P->info, Q->info);

4. Swap two adjacent elements by adjusting only the links (and not the data) using:
a) singly linked lists
b) doubly linked lists

Provide a diagram or steps of your solution.

Programming:
You were tasked to do programs that will illustrate the use of lists implemented using an
array. One uses a sorted list, the other an unsorted list. Run the programs separately
and answer the following questions.

37
1. Write a program that will initially create an UNSORTED list containing the following and
then run the program:

Mitch
Diane
Jack
Robbie
Katherine

Answer and explain the following questions below:


a) If we check the memory location of each element in the list, what would it be? What
index represent each element?
b) What happens if we add Morrie in the list? What will be its index value?
c) What does the new list look like? Where do you think Morrie should be placed and
why?

With the same list above (with Morrie added), delete/remove Jack. Answer and explain
the following questions below:
a) What is the new list? Identify the elements of the list and its index.
b) What happened to the former location occupied by Jack?

2. Write a program that will initially create a SORTED list containing the following and then
run the program:

Diane
Jack
Katherine
Mitch
Robbie

Answer and explain the following questions below:


a) If we check the memory location of each element in the list, what would it be? What
index represent each element?
b) What happens if we add Morrie in the list? What will be its index value?
c) What does the new list looks like? Where do you think Morrie should be placed
and why?

With the same list above (with Morrie added), delete/remove Jack. Answer and explain
the following questions below:
a) What is the new list? Identify the elements of the list and its index.
b) What happened to the former location occupied by Jack?

3. In most personal computers, the largest integer is 32,767 and the largest long integer is
2,147,483,647. Some applications, such as the national debt, may require an unbounded
integer. One way to store and manipulate integers of unlimited size is by using a linked
list. Each digit is stored in a node of the list. For example, the figure below shows how
we could store a five-digit number in a linked list.

38
While the linked list in the figure is represented as moving from right to left, remember that
there is no physical direction in a linked list. We represent it this way to assist us in
understanding the problem.

To add two numbers, we simply add the corresponding digit in the same location in their
respective linked lists with the carry from the previous addition. With each addition, if the
sum is greater than 10, then we need to subtract ten and set the carry to one. Otherwise,
the carry is set to zero.

Write a program to add two integer linked lists. Design your solution so that the same
logic adds the first numbers (units’ position) as well as the rest of the number. In other
words, do not have special one-time logic for adding the units’ position.

4. Write a program in C to create and display a circular linked list.


Test Data :
Input the number of nodes : 3
Input data for node 1 : 2
Input data for node 2 : 5
Input data for node 3 : 8
Expected Output :

Data entered in the list are :


Data 1 = 2
Data 2 = 5
Data 3 = 8

39
Unit 5 – Stacks

Overview:
Stacks are dynamic data structures that follow the Last In First Out (LIFO) principle. The last item
to be inserted into a stack is the first one to be deleted from it. In this unit we will explore stacks
and its applications and learn how to implement it.

Module Objectives:
After successful Completion of this module, you should be able to:

• Define and describe stacks and its characteristics


• Discuss the different operations on stack
• Develop your own implementation of stack
• Apply your stack implementation in solving programming challenges

Course Materials:

A stack is an Abstract Data Type (ADT), commonly used in most programming languages.
It is named stack as it behaves like a real-world stack, for example – a deck of cards or a pile of
plates, etc. A real-world stack allows operations at one end only. For example, we can place or
remove a card or plate from the top of the stack only.

Figure 25 Stack

40
A stack is a container of objects that are inserted and removed according to the last-in
first-out (LIFO) principle. In the pushdown stacks only two operations are allowed: push the item
into the stack and pop the item out of the stack. A stack is a limited access data structure as
elements can be added and removed from the stack only at the top. Push adds an item to the top
of the stack, pop removes the item from the top. A helpful analogy is to think of a stack of books;
you can remove only the top book, also you can add a new book on the top.

Applications of Stacks

Here are some examples where stacks can be applied.

1. The simplest application of a stack is to reverse a word. You push a given word to a
stack one character at a time and then pop letters from the stack.

2. Another application is an "undo" mechanism in text editors; this operation is


accomplished by keeping all text changes in a stack.

3. Problems involving Backtracking also uses stack. Backtracking is a process when you
need to access the most recent data element in a series of elements. Think of a
labyrinth or maze - how do you find a way from an entrance to an exit?

Figure 26 Backtracking applied to a maze problem

Once you reach a dead end, you must backtrack. But backtrack to where? to the
previous choice point. Therefore, at each choice point you store on a stack all possible
choices. Then backtracking simply means popping a next choice from the stack.

4. Stacks can also be used in Language processing:

a. space for parameters and local variables is created internally using a stack.
b. compiler's syntax check for matching braces is implemented by using stack.
c. support for recursion

41
Stack Representation and Implementation

A Stack Data Structure can be implemented in two ways: array implementation and linked
list implementation.

Array-based Implementation

In an array-based implementation we maintain the following fields: an array A of a


default size (≥ 1), the variable top that refers to the top element in the stack and the
capacity that refers to the array size.

The variable top changes from - 1 to capacity - 1. We say that a stack is empty
when top = -1, and the stack is full when top = capacity - 1.

Figure 27 Array-based implementation of Stack

In a fixed-size stack, the capacity stays unchanged, therefore when top reaches
capacity, the stack throws an overflow error. Whereas, in a dynamic stack when top
reaches capacity, we double up the stack size.
An advantage of an array-based implementation is that it is easy to implement,
and that memory is saved as pointers are not involved. The drawback is that it is not
dynamic. It doesn’t grow and shrink depending on needs at runtime.

Linked List-based Implementation

Linked List-based implementation provides the best (from the efficiency point of
view) dynamic stack implementation. The linked list implementation of a stack can grow
and shrink according to the needs at runtime. This advantage also closes into its
disadvantage that is it requires extra memory due to involvement of pointers.

Figure 28 Linked List-based implementation of Stack

Basic Operations

Stack operations may involve initializing the stack, using it and then de-initializing it. Apart
from these basic stuffs, a stack is used for the following two primary operations:

42
• push() − Pushing (storing) an element on the stack.
• pop() − Removing (accessing) an element from the stack.

To use a stack efficiently, we need to check the status of stack as well. For the same
purpose, the following functionality is added to stacks −

• peek() − get the top data element of the stack, without removing it.
• isFull() − check if stack is full.
• isEmpty() − check if stack is empty.

At all times, we maintain a pointer to the last PUSHed data on the stack. As this pointer
always represents the top of the stack, hence named top. The top pointer provides top value of
the stack without actually removing it.

Operations to support stack functions showing the procedures.

peek()

Algorithm of peek() function −

begin procedure peek


return stack[top]
end procedure

Implementation of peek() function in C programming language −

int peek() {
return stack[top];
}

isfull()

Algorithm of isfull() function −

begin procedure isfull

if top equals to MAXSIZE


return true
else
return false
endif

end procedure

43
Implementation of isfull() function in C programming language −

bool isfull() {
if(top == MAXSIZE)
return true;
else
return false;
}

isempty()

Algorithm of isempty() function −

begin procedure isempty

if top less than 1


return true
else
return false
endif

end procedure

Implementation of isempty() function in C programming language is slightly different. We


initialize top at -1, as the index in array starts from 0. So we check if the top is below zero
or -1 to determine if the stack is empty. Here's the code −

bool isempty() {
if(top == -1)
return true;
else
return false;
}

Push Operation

The process of putting a new data element onto stack is known as a Push Operation. Push
operation involves a series of steps −

Step 1 − Checks if the stack is full.


Step 2 − If the stack is full, produces an error and exit.
Step 3 − If the stack is not full, increments top to point next empty space.
Step 4 − Adds data element to the stack location, where top is pointing.
Step 5 − Returns success.

44
Figure 29 Stack Push Operation

If the linked list is used to implement the stack, then in step 3, we need to allocate space
dynamically.

A simple algorithm for Push operation can be derived as follows −

begin procedure push: stack, data

if stack is full
return null
endif

top ← top + 1
stack[top] ← data

end procedure

Implementation of this algorithm in C, is very easy. See the following code −

void push(int data) {


if(!isFull()) {
top = top + 1;
stack[top] = data;
} else {
printf("Could not insert data, Stack is full.\n");
}
}

45
Pop Operation

Accessing the content while removing it from the stack, is known as a Pop Operation. In
an array implementation of pop() operation, the data element is not actually removed, instead top
is decremented to a lower position in the stack to point to the next value. But in a linked-list
implementation, pop() actually removes data element and deallocates memory space. A Pop
operation may involve the following steps −

Step 1 − Checks if the stack is empty.


Step 2 − If the stack is empty, produces an error and exit.
Step 3 − If the stack is not empty, accesses the data element at which top is pointing.
Step 4 − Decreases the value of top by 1.
Step 5 − Returns success.

Figure 30 Stack Pop Operation

A simple algorithm for Pop operation can be derived as follows −

begin procedure pop: stack


if stack is empty
return null
endif

data ← stack[top]
top ← top - 1
return data
end procedure

Implementation of this algorithm in C, is as follows −

int pop(int data) {


if(!isempty()) {
data = stack[top];
top = top - 1;
return data;
} else {
printf("Could not retrieve data, Stack is empty.\n");
}
}

46
Arithmetic Expression Evaluation

The stack organization is very effective in evaluating arithmetic expressions. Expressions


are usually represented in what is known as notation. An arithmetic expression can be written in
three different but equivalent notations, i.e., without changing the essence or output of an
expression. These notations are −

• Infix Notation
• Prefix (Polish) Notation
• Postfix (Reverse-Polish) Notation

These notations are named as how they use operator in expression.

Infix Notation

Each operator is written between two operands (i.e., A + B). It is easy for us humans to
read, write, and speak in infix notation but the same does not go well with computing devices. An
algorithm to process infix notation could be difficult and costly in terms of time and space
consumption. With this notation, we must distinguish between ( A + B ) * C and A + ( B * C ) by
using either parentheses or some operator-precedence convention. Thus, the order of operators
and operands in an arithmetic expression does not uniquely determine the order in which the
operations are to be performed.

Prefix Notation

In this notation, operator is placed before its two operands. For example, +ab, this is
equivalent to its infix notation a + b. Prefix notation is also known as Polish Notation and no
parentheses are required.

Postfix Notation

This notation style is known as Reversed Polish Notation. It refers to the analogous
notation in which the operator is placed after its two operands. For example, ab+, this is equivalent
to its infix notation a + b. Again, no parentheses is required in Reverse Polish notation

Expression Parsing

Stack organized computers are better suited for post-fix notation than the traditional infix
notation. Thus the infix notation must be converted to the post-fix notation. An important
application of stacks is in parsing. For example, a compiler must parse arithmetic expressions
written using infix notation:

1 + ((2 + 3) * 4 + 5) * 6

We break the problem of parsing infix expressions into two stages. First, we convert from
infix to a different representation called postfix. Then we parse the postfix expression, which is a

47
somewhat easier problem than directly parsing infix. The conversion from infix notation to post-
fix notation must take into consideration the operational hierarchy.

There are 3 levels of precedence for 5 binary operators as given below:

• Highest: Exponentiation (^)


• Next highest: Multiplication (*) and division (/)
• Lowest: Addition (+) and Subtraction (-)

For example –

Infix notation: ( A – B ) * [ C / ( D + E ) + F ]

Post-fix notation: A B – C D E + / F + *

Here, we first perform the arithmetic inside the parentheses (A-B) and (D+E). The division
of C/(D+E) must done prior to the addition with F. After that multiply the two terms inside the
parentheses and bracket.

Converting from Infix to Postfix

Typically, we deal with expressions in infix notation

2 + 5

where the operators (e.g. +, *) are written between the operands (e.q, 2 and 5). Writing the
operators after the operands gives a postfix expression 2 and 5 are called operands, and the '+'
is operator. The above arithmetic expression is called infix, since the operator is in between
operands. The expression below is postfix since the operator is found after the operands.

2 5 +

Writing the operators before the operands gives a prefix expression

+ 2 5

Suppose you want to compute the cost of your shopping trip. To do so, you add a list of
numbers and multiply them by the local sales tax (7.25%):

70 + 150 * 1.0725

48
Depending on the calculator, the answer would be either 235.95 or 230.875. To avoid this
confusion we shall use a postfix notation

70 150 + 1.0725 *

Postfix has the nice property that parentheses are unnecessary.

Now, we describe how to convert from infix to postfix.

1. Read in the tokens one at a time

2. If a token is an integer, write it into the output

3. If a token is an operator, push it to the stack, if the stack is empty. If the stack is not empty,
you pop entries with higher or equal priority and only then you push that token to the stack.

4. If a token is a left parentheses '(', push it to the stack

5. If a token is a right parentheses ')', you pop entries until you meet '('.

6. When you finish reading the string, you pop up all tokens which are left there.

7. Arithmetic precedence is in increasing order: '+', '-', '*', '/';

Example. Suppose we have an infix expression:2+(4+3*2+1)/3. We read the string by characters.

'2' - send to the output.


'+' - push on the stack.
'(' - push on the stack.
'4' - send to the output.
'+' - push on the stack.
'3' - send to the output.
'*' - push on the stack.
'2' - send to the output.

Evaluating a Postfix Expression

We describe how to parse and evaluate a postfix expression.

1. We read the tokens in one at a time.

2. If it is an integer, push it on the stack

3. If it is a binary operator, pop the top two elements from the stack, apply the operator, and
push the result back on the stack.

49
Consider the following postfix expression

5 9 3 + 4 2 * * 7 + *

Here is a chain of operations

Stack Operations Output


--------------------------------------
push(5); 5
push(9); 5 9
push(3); 5 9 3
push(pop() + pop()) 5 12
push(4); 5 12 4
push(2); 5 12 4 2
push(pop() * pop()) 5 12 8
push(pop() * pop()) 5 96
push(7) 5 96 7
push(pop() + pop()) 5 103
push(pop() * pop()) 515

Note, that division is not a commutative operation, so 2/3 is not the same as 3/2.

Watch:

• Introduction to Stacks By mycodeschool (https://youtu.be/F1F2imiOJfk)


• Stacks By CS50 (https://youtu.be/hVsNqhEthOk)
• Array implementation of stacks By mycodeschool (https://youtu.be/sFVxsglODoo)
• Linked list implementation of stacks By mycodeschool (https://youtu.be/MuwxQ2IB8lQ)
• Infix, Prefix and Postfix By mycodeschool (https://youtu.be/jos1Flt21is)

Read:

• Section 3.6 The Stack ADT (pp. 103-112)


Weiss, Mark Allen (2014). Data Structures And Algorithm Analysis In C++ 4th Edition

• Section 6.1 Stacks (pp.226-237)


Goodrich, Michael T. (2014). Data Structures and Algorithms in Java 6th Edition

• Section 1.3 Bags, Queues, and Stacks (pp. 120-157)


Sedgewick, Robert, Wayne, Kevin (2011). Algorithms 4th Edition

Review:

1. What is a stack and where it can be used?


2. What are the different ways to implement stacks?

50
3. How is a stack similar to a list? How is it different?
4. Give two stack operations, and what do these operations do?
5. What is one advantage of using a linked list to implement a stack rather than an array?
What is one disadvantage?

Activities/Assessments:
1. Many real-world situations correspond to a stack. For example, a pile of trays in a cafeteria
is a stack since the last tray put on the pile is the first tray used. Think of other real-world
stacks and describe the push and pop operations on these.

2. Determine what is ask for the following table:

Infix Postfix Prefix

1 4 * B + C – 5 4 B * C + 5 - - + * 4 B C 5

2 A + B * C - D A B C * + D – - + A * B C D

3 A + B * C ^ D A B C D ^ * + + A * B ^ C D

4 A * B + C – (D – E) A B * C + D E - - - + * A B C – D E

5 3 * 4 + 5 / 2 - 3 3 4 * 5 2 / + 3 - – + * 3 4 / 5 2 3

3. A letter means push and an asterisk means pop in the following sequence. Give the
sequence of values returned by the pop operations when this sequence of operations is
performed on an initially empty LIFO stack.

E A S * Y * Q U E * * * S T * * * I O * N * * *

4. A letter means push and an asterisk means pop in the following sequence. Give the
contents of s[0], ..., s[4] after the execution of this sequence of operations performed on
an initially empty LIFO stack.

L A * S T I * N * F I R * S T * * O U * T * * * * * *

5. Suppose that a client performs an intermixed sequence of (stack) push and pop
operations. The push operations push the integers 0 through 9 in order on to the stack;
the pop operations print out the return value. Circle the following sequence(s) that could
not occur.
a. 4 3 2 1 0 9 8 7 6 5
b. 2 1 4 3 6 5 8 7 9 0
c. 0 4 6 5 3 8 1 7 2 9
d. 4 6 8 7 5 3 2 9 0 1

51
Programming:

1. Write a program that will continuously accept a character and store in a stack until letter
‘Z’ is entered.

2. Write a program that will continuously accept a character and store in a stack until a vowel
is entered, then display the letters in reverse order of their arrival.

3. Write a program that will continuously accept a character and store in a stack until an
asterisk (*) is entered, and then display the third to the last character entered.

4. Write a program that takes from standard input an expression without left parentheses
and prints the equivalent infix expression with the parentheses inserted. For example,
given the input:
1 + 2 ) * 3 - 4 ) * 5 - 6 ) ) )
your program should print
( ( 1 + 2 ) * ( ( 3 - 4 ) * ( 5 - 6 ) )

5. Write a program that takes an infix expression from standard input, evaluates it, and prints
the value. (Piping the output of your program from the previous exercise to this program
gives equivalent behavior to Evaluate.

6. Write a program InfixToPostfix that converts an arithmetic expression from infix to postfix.

7. Write a program EvaluatePostfix that takes a postfix expression from standard input,
evaluates it, and prints the value.

52
Unit 6 - Queues

Overview:
Queues are data structures that follow the First In First Out (FIFO) principle where in the first
element that is added to the queue is the first one to be removed. In this unit we will explore
queues and its applications and learn how to implement it.

Module Objectives:
After successful Completion of this module, you should be able to:

• Define and describe queue and its characteristics


• Discuss the different operations on queue
• Develop your own implementation of queue
• Apply your queue implementation in solving programming challenges

Course Materials:

Queue is an abstract data structure, somewhat similar to Stacks. Unlike stacks, a queue
is open at both ends. One end (REAR; also called tail) is always used to insert data (enqueue)
and the other (FRONT; also called head) is used to remove data (dequeue). Queue follows First-
In-First-Out methodology, i.e., the data item stored first will be accessed first. A real-world
example of queue can be a single-lane one-way road, where the vehicle enters first, exits first.
More real-world examples can be seen as queues at the ticket windows and bus-stops.

Figure 31 Real-world example of Queue (Single-lane One-way road)

In the queue only two operations are allowed enqueue and dequeue. Enqueue means to
insert an item into the back of the queue, dequeue means removing the front item. The picture
demonstrates the FIFO access.

The difference between stacks and queues is in removing. In a stack we remove the item
the most recently added; in a queue, we remove the item the least recently added.

53
Application of Queues

Queue, as the name suggests is used whenever we need to manage any group of objects
in an order in which the first one coming in, also gets out first while the others wait for their turn,
like in the following scenarios:

1. Serving requests on a single shared resource, like a printer, CPU task scheduling etc.

2. In real life scenario, Call Center phone systems uses Queues to hold people calling
them in an order, until a service representative is free.

3. Handling of interrupts in real-time systems. The interrupts are handled in the same
order as they arrive i.e First come first served.

Queue Representation and Implementation

Queue can be implemented using an Array, Stack or Linked List. The easiest way of
implementing a queue is by using an Array.

Initially the head (FRONT) and the tail (REAR) of the queue points at the first index of the
array (starting the index of array from 0). As we add elements to the queue, the tail keeps on
moving ahead, always pointing to the position where the next element will be inserted, while the
head remains at the first index.

Figure 32 Implementation of Queue

54
When we remove an element from Queue, we can follow two possible approaches
(mentioned [A] and [B] in above diagram). In [A] approach, we remove the element at head
position, and then one by one shift all the other elements in forward position.

In approach [B] we remove the element from head position and then move head to the
next position.

In approach [A] there is an overhead of shifting the elements one position forward every
time we remove the first element.

In approach [B] there is no such overhead, but whenever we move head one position
ahead, after removal of first element, the size on Queue is reduced by one space each time.

Circular Queue

In order to resolve the problem with [B] we can implement what we call a circular
queue. Given an array A of a default size (≥ 1) with two references back and front,
originally set to -1 and 0 respectively. Each time we insert (enqueue) a new item, we
increase the back index; when we remove (dequeue) an item - we increase the front index.
Here is a picture that illustrates the model after a few steps:

Figure 33 Circular Queue implementation

As you see from the picture, the queue logically moves in the array from left to
right. After several moves back reaches the end, leaving no space for adding new
elements

Figure 34 Circular queue (end of linear list reached)

However, there is a free space before the front index. We shall use that space for
enqueueing new items, i.e. the next entry will be stored at index 0, then 1, until front. Such
a model is called a wrap-around queue or a circular queue.

55
Figure 35 Circular queue (wrap around)

Finally, when back reaches front, the queue is full. There are two choices to handle
a full queue: a) throw an overflow error ; b) double the array size (increase storage space).

The circular queue implementation is done by using the modulo operator


(denoted %), which is computed by taking the remainder of division (for example, 8%5 is
3). By using the modulo operator, we can view the queue as a circular array, where the
"wrapped around" can be simulated as "back % array_size". In addition to the back and
front indexes, we maintain another index: current - for counting the number of elements in
a queue. Having this index simplifies a logic of implementation.

Basic Operations

Queue operations may involve initializing or defining the queue, utilizing it, and then
completely erasing it from the memory. Here we shall try to understand the basic operations
associated with queues −

• enqueue() − add (store) an item to the queue.


• dequeue() − remove (access) an item from the queue.

Few more functions are required to make the above-mentioned queue operation efficient.
These are −

• peek() − Gets the element at the front of the queue without removing it.
• isfull() − Checks if the queue is full.
• isempty() − Checks if the queue is empty.

In queue, we always dequeue (or access) data, pointed by front pointer and while
enqueueing (or storing) data in the queue we take help of rear pointer.

Let's first learn about supportive functions of a queue −

peek()

This function helps to see the data at the front of the queue. The algorithm of peek()
function is as follows −

begin procedure peek


return queue[front]
end procedure

56
Implementation of peek() function in C programming language −

int peek() {
return queue[front];
}

isfull()

As we are using single dimension array to implement queue, we just check for the rear
pointer to reach at MAXSIZE to determine that the queue is full. In case we maintain the
queue in a circular linked-list, the algorithm will differ. Algorithm of isfull() function −

begin procedure isfull


if rear equals to MAXSIZE
return true
else
return false
endif
end procedure

Implementation of isfull() function in C programming language −

bool isfull() {
if(rear == MAXSIZE - 1)
return true;
else
return false;
}

isempty()

Algorithm of isempty() function −

begin procedure isempty

if front is less than MIN OR front is greater than rear


return true
else
return false
endif

end procedure

57
If the value of front is less than MIN or 0, it tells that the queue is not yet initialized, hence
empty. Here's the C programming code −

bool isempty() {
if(front < 0 || front > rear)
return true;
else
return false;
}

Enqueue Operation

Queues maintain two data pointers, front and rear. Therefore, its operations are
comparatively difficult to implement than that of stacks. The following steps should be taken to
enqueue (insert) data into a queue −

Step 1 − Check if the queue is full.


Step 2 − If the queue is full, produce overflow error and exit.
Step 3 − If the queue is not full, increment rear pointer to point the next empty space.
Step 4 − Add data element to the queue location, where the rear is pointing.
Step 5 − return success.

Figure 36 Queue Insert Operation (Enqueue)

Sometimes, we also check to see if a queue is initialized or not, to handle any unforeseen
situations.

Algorithm for enqueue operation

procedure enqueue(data)
if queue is full
return overflow
endif

rear ← rear + 1
queue[rear] ← data
return true
end procedure

58
Implementation of enqueue() in C programming language −

int enqueue(int data)


if(isfull())
return 0;

rear = rear + 1;
queue[rear] = data;

return 1;
end procedure

Dequeue Operation

Accessing data from the queue is a process of two tasks − access the data where front is
pointing and remove the data after access. The following steps are taken to perform dequeue
operation −

Step 1 − Check if the queue is empty.


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.
Step 4 − Increment front pointer to point to the next available data element.
Step 5 − Return success.

Figure 37 Queue Remove Operation (Dequeue)

Algorithm for dequeue operation

procedure dequeue
if queue is empty
return underflow
end if

data = queue[front]
front ← front + 1
return true
end procedure

59
Implementation of dequeue() in C programming language −

int dequeue() {
if(isempty())
return 0;

int data = queue[front];


front = front + 1;

return data;
}

Watch:

• Introduction to Queues By mycodeschool (https://youtu.be/XuCbpw6Bj1U)


• Queues By CS50 (https://youtu.be/3TmUv1uS92s)
• Array implementation of queues By mycodeschool (https://youtu.be/okr-XE8yTO8)
• Linked list implementation of queues By mycodeschool (https://youtu.be/A5_XdiK4J8A)
• Circular Queue Implementation – Array By Blue Tree Code (https://youtu.be/8sjFA-IX-
Ww)

Read:

• Section 3.7 The Queue ADT (pp. 112-116)


Weiss, Mark Allen (2014). Data Structures And Algorithm Analysis In C++ 4th Edition

• Section 6.2 Queues (pp.238-247)


Goodrich, Michael T. (2014). Data Structures and Algorithms in Java 6th Edition

• Section 1.3 Bags, Queues, and Stacks (pp. 120-157)


Sedgewick, Robert, Wayne, Kevin (2011). Algorithms 4th Edition

Review:

1. What is a queue? How is it different from stack?


2. How are queues implemented?
3. Give two queue operations, and what do these operations do?
4. What are the different types of queues?
5. What is the advantage of using the linked list implementation of queues, as opposed to
the array implementation?

60
Activities/Assessments:
1. Suppose that Q is an initially empty array-based queue of size 5. Show the values of the
data members front and back after each statement has been executed. Indicate any errors
that might occur.

Queue<Character> Q( 5 ); front = _____ back = _____

Q.enqueue ( ‘A’ ); front = _____ back = _____

Q.enqueue ( ‘B’ ); front = _____ back = _____

Q.enqueue ( ‘C’ ); front = _____ back = _____

char c = Q.dequeue( ); front = _____ back = _____

Q.enqueue ( ‘A’ ); front = _____ back = _____

2. A letter means enqueue and an asterisk means dequeue in the following sequence. Give
the sequence of values returned by the dequeue operations when this sequence of
operations is performed on an initially empty FIFO queue.

E A S * Y * Q U E * * * S T * * * I O * N * * *

3. Suppose that a client performs an intermixed sequence of (queue) enqueue and dequeue
operations. The enqueue operations put the integers 0 through 9 in order onto the queue;
the dequeue operations print out the return value. Which of the following sequence(s)
could not occur?
a. 0 1 2 3 4 5 6 7 8 9
b. 4 6 8 7 5 3 2 9 0 1
c. 2 5 6 7 4 8 9 3 1 0
d. 4 3 2 1 0 5 6 7 8 9

Programming:
1. The Josephus problem is the following game: N people, numbered 1 to N, are sitting in a
circle. Starting at person 1, a hot potato is passed. After M passes, the person holding the
hot potato is eliminated, the circle closes ranks, and the game continues with the person
who was sitting after the eliminated person picking up the hot potato. The last remaining
person wins. Thus, if M = 0 and N = 5, players are eliminated in order, and player 5 wins.
If M = 1 and N = 5, the order of elimination is 2, 4, 1, 5.

a) Write a program to solve the Josephus problem for general values of M and N. Try
to make your program as efficient as possible. Make sure you dispose of cells.

b) What is the running time of your program?

c) If M = 1, what is the running time of your program? How is the actual speed affected
by the delete routine for large values of N (N > 100,000)?

61

You might also like