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

DataStructureandPseudocode

The document provides an overview of a module focused on Pseudocode, Data Structures, and Algorithms, emphasizing their importance in computer science and problem-solving. It outlines the module's objectives, including understanding algorithms, implementing sorting and searching techniques, and utilizing various data structures like linked lists, stacks, queues, and trees. The document also discusses the significance of choosing appropriate algorithms and data structures for efficient problem-solving, alongside practical examples and activities to reinforce learning.

Uploaded by

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

DataStructureandPseudocode

The document provides an overview of a module focused on Pseudocode, Data Structures, and Algorithms, emphasizing their importance in computer science and problem-solving. It outlines the module's objectives, including understanding algorithms, implementing sorting and searching techniques, and utilizing various data structures like linked lists, stacks, queues, and trees. The document also discusses the significance of choosing appropriate algorithms and data structures for efficient problem-solving, alongside practical examples and activities to reinforce learning.

Uploaded by

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

Module: Pseudocode, Data Structures, and Algorithms

Module Overview
Data Structures and Algorithms are fundamental concepts in computer science. Data structures organize and store data, such as arrays
and linked lists. Algorithms are step-by-step instructions to solve problems, including searching and sorting tasks. They're vital for
efficient software and excelling in technical interviews.

Module Objective
At the end of this module, students should be able to demonstrate appropriate knowledge and show an
understanding of the following:
Introducing Pseudocode, Algorithms and Data Structures
Implementing Sorting and Searching Algorithms
Problem-Solving with Linked Lists
Problem-Solving with Stacks and Queues
Problem-Solving with Trees
Graphs in Data Structures

Exploring the Role of Algorithms and Data Structures in Problem-Solving

Problem-solving is essential in various scientific fields, and computers play a vital role in addressing challenges across
domains like banking, medicine, and manufacturing. To solve problems using computers, programs are created with
algorithms and data structures. Different algorithms and data structures can offer solutions to the same problem.
However, optimal efficiency is achieved by carefully selecting algorithm-data structure combinations.

Role of Algorithms

Algorithms serve as fundamental tools in problem-solving and computer science, offering structured and dependable
approaches:

• Algorithm Origins: The term "algorithm" traces its roots to Al Khwarizmi, an influential Persian mathematician. His
legacy paved the way for systematic and organized methods of addressing challenges.

• Methodical Process: At its core, an algorithm represents a meticulously crafted sequence of steps. This methodical
design guide problem-solvers through a logical journey, enhancing efficiency and effectiveness.

• Precision and Reliability: Algorithms stand as beacons of precision and reliability. By adhering to predetermined
instructions, they ensure consistent and accurate outcomes, which is crucial in domains like research and data
analysis.

• Consistent Results: The hallmark of algorithms is their consistent delivery of correct results. By minimizing the
potential for human error, algorithms bolster the dependability of problem-solving, fostering informed decision-
making and trustworthy solutions.

• In essence, algorithms are the bedrock of contemporary problem-solving, harnessing structured approaches to
tackle diverse challenges across various sectors.
Illustrative Example: Displaying Natural Numbers

Consider the following step-by-step procedure to display the first

10 natural numbers:

1. Set the value of the counter to 1.

2. Display counter.

3. Increment counter by 1.

4. If counter <= 10, go to step 2.

The preceding step-by-step procedure is an algorithm because it produces the correct result with a finite number of steps.

An algorithm possesses five crucial properties:

• Finiteness: It concludes within a finite number of steps.

• Definiteness: Each step in the algorithm is clear and free from ambiguity. This ensures that the action described by
each step has a single interpretation and can be executed without any confusion.

• Input: The algorithm can take zero or more inputs as information to work with.

• Output: It generates at least one output as a result of its execution.

• Effectiveness: The algorithm comprises basic instructions that are achievable. This implies that the provided
instructions can be carried out using the given inputs within a specific amount of time.

Algorithmic Advantages: Unveiling the Benefits

• Crafting an algorithm involves identifying step-by-step procedures, crucial decision points, and necessary variables,
aiding the development of corresponding computer programs.

• Procedure identification and decision points break down complex problems into manageable subproblems,
enabling a systematic approach to challenging tasks.

• Algorithms ensure uniformity by utilizing the same specified steps for task execution, enhancing consistency and
reliability.

• Consistent algorithmic processes promote rational decision-making devoid of human biases and misjudgments,
enhancing objectivity.

Role of Data Structures

In the realm of problem-solving, a single issue can often be tackled using multiple algorithms, each with varying degrees of
efficiency. Choosing the most suitable algorithm becomes pivotal to attain the highest level of effectiveness in terms of
both time and memory consumption.
The selection of the right algorithm can significantly impact the overall efficiency of the solution. However, an equally
important aspect lies in the organization of the data that these algorithms operate upon. This organization, known as a
data structure, plays a fundamental role in enhancing algorithmic efficiency. By strategically arranging data elements, we
create a foundation that empowers algorithms to perform their tasks more swiftly and with reduced resource usage.

There are diverse approaches to organizing data, with some well-established options. These include arrays, linked lists,
stacks, queues, and trees, which are considered standard data structures.

Data structures exhibit variations in how data elements are organized and the operations they enable. These distinctions
directly influence the efficiency of problem-solving processes. Depending on the specific requirements of a task, selecting
an appropriate data structure can significantly enhance the effectiveness of an algorithm.

For instance, let's consider a practical scenario: an algorithm designed to manage print requests from multiple users on a
first-come-first-served basis. In this case, making a judicious choice of a data structure is of paramount importance. The
selected data structure should ideally store and retrieve requests in the order of their arrival, ensuring optimal efficiency
for this particular use case.

To elevate an algorithm's efficiency, it's crucial to employ fitting data structures. This strategy not only improves
performance but also tackles the subsequent programming challenges:

• Simplifying intricate problems: Utilizing appropriate data structures simplifies the handling of complex issues,
making problem-solving more streamlined and effective.

• Developing standardized and reusable code components: The use of suitable data structures promotes the
creation of standardized building blocks that can be reused across various parts of a program, reducing
redundancy and enhancing efficiency.

• Crafting programs that are both comprehensible and maintainable: By choosing the right data structures,
programs become more organized and easier to understand, leading to better maintenance and long-term
viability.

Significance of Using Data Structures for Problem-Solving

Example: Finding maximum among 50 numbers: Consider a scenario where you need to determine the highest value
within a collection of 50 numbers.

Choice: 50 variables or an array (size 50): You're presented with a choice between utilizing individual variables for each
number or employing a structured data storage approach, such as an array with a capacity of 50 elements.

This strategic decision can lead to:

Efficient problem-solving through a suitable data structure: Opting for the right data structure can significantly improve the
efficiency and effectiveness of your problem-solving approach. By making an informed choice, you can streamline
processes, enhance performance, and simplify complex tasks.

Example:

To understand the use of an appropriate data structure, which helps in simplifying the solution to a problem, let us
consider an example where you have to find the maximum value in a set of 50 numbers. In such a case, you can either use
50 variables or use a data structure, such as an array of size 50, to store the numbers.
When 50 different variables are used to store the numbers, the following algorithm can be used to determine the
maximum value among the numbers:

1. Accept 50 numbers and store them in num1, num2, num3, …, num50.

2. Set max = num1.

3. If num2 > max then:

max = num2

4. If num3 > max then:

max = num3

5. If num4 > max then:

max = num4

6. If num50 > max then:

max = num50

7. Display max.

Explanation:

When opting for 50 individual variables to store the numbers, you can implement the following algorithm to identify the
maximum value among the numbers:

In the scenario where 50 distinct variables are employed to hold the individual numbers, a specific algorithm can be
utilized to ascertain the maximum value within the set. The algorithm operates as follows:

Obtain 50 numbers and designate them as num1, num2, num3, ..., num50.

Initialize a variable named "max" and set its value equal to num1.

Compare num2 with the current "max" value:

If num2 is greater than "max," update "max" to num2.

Perform the same comparison and update process for num3 and "max."

Repeat the comparison and update for num4 and "max."

Continue this process for num5, num6, ..., num50, updating "max" if a larger number is encountered.

Once all numbers have been compared, the final value of "max" will represent the maximum value within the set.

Display the calculated "max" value.


This algorithm iterates through the numbers, progressively updating the "max" value whenever a larger number is
encountered. Ultimately, the algorithm identifies and displays the maximum value among the given set of numbers.

On the other hand, when an array of size 50 is used, the following algorithm can be used to determine the maximum
value among the elements in an array:

1. Set max = num[0].

2. Repeat step 3 varying i from 1 to 49.

3. If num[i] > max then:

max = num[i]

4. Display max.

Explanation:

In contrast, when an array of size 50 is employed, a different algorithm can be employed to ascertain the maximum value
among the array elements:

Initialize a variable named "max" and set its value equal to the first element of the array, i.e., num[0].

Iterate through the array, starting from the second element (num[1]) up to the 49th element (num[49]).

For each iteration, compare the current element (num[i]) with the current "max" value:

If num[i] is greater than "max," update "max" to the value of num[i].

After iterating through all elements, the final value of "max" will represent the maximum value within the array.

Display the calculated "max" value.

This algorithm systematically compares each element of the array with the current "max" value, updating "max" whenever
a larger element is encountered. Ultimately, the algorithm identifies and displays the maximum value present in the array
of size 50.

The comparison between the two algorithms:

Algorithm using 50 Algorithm using Array of Size


Comparison Variables 50

Memory Efficiency Less efficient More efficient

Steps More steps Fewer steps

Implementation Complexity Higher complexity Lower complexity


Reusability of Code
Components Limited Enhanced

Maintenance Simplification Less standardized Standardized and simplified

Types of Data Structures

• Static Data Structures refer to data arrangements with a predetermined size set during compilation. Arrays are a
common example of static data structures. However, the fixed size of these structures can lead to memory
inefficiencies when space remains unused. Additionally, adjusting their size at runtime is challenging, often
requiring significant modifications.

• Dynamic Data Structures are characterized by their adaptable size, which is not predetermined during
compilation. An example of such a structure is a linked list. Unlike static data structures, dynamic ones optimize
memory utilization by allocating and deallocating memory as required. This flexibility ensures efficient memory
management and allows for better responsiveness to changing data needs.

Activity 1: Problem-Solving Using Algorithms

Problem Statement

Write an algorithm to accept a number and check whether the given number is an Armstrong number. In addition, write a
program to implement this algorithm. A number is called an Armstrong number if the sum of the cubes of its digits is the
same as the number.

Solution

The following algorithm depicts the logic to check whether a given number, n, is an Armstrong number:

1. Accept the value of n.

2. Set temp=n, arm=0, rem=0.

3. Repeat the steps, a, b, and c, until n>0.

a. Set rem=n%10.

b. Set arm=arm+ (rem*rem*rem).

c. Set n=n/10.

4. If temp=arm, then:

Display “It is an Armstrong number.”

Else:

Display “It is not an Armstrong number.”


Solution in Java

To check whether the given number is an Armstrong number, you need to perform the following tasks:

1. Write the code in NetBeans.

2. Execute the code and verify the output.

Code:

package armstrongnumber;

import java.util.*;

public class ArmstrongNumber {

public static void main(String[] args) {

int num, temp, rem, arm = 0;

System.out.println("Enter a number");

num = Integer.parseInt(new Scanner(System.in).nextLine());

temp = num;
while (num > 0)

rem = num % 10;

arm = arm + (rem * rem * rem);

num = num / 10;

if (temp == arm)

System.out.println("It is an Armstrong number");

else

System.out.println("It is not an Armstrong number");

new Scanner(System.in).nextLine();

}
}

What Is Pseudo-Code?

The pseudocode is an informal way of writing a program for better human understanding. It is written in simple English,
making the complex program easier to understand.

Pseudocode cannot be compiled or interpreted. It doesn't follow the programming language's syntax; it is thus written in
pseudocode so that any programmers or non-programmers can easily understand it.

Consider the following source code example:

int n = 10;

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

System.out.println(n);

The above source code is converted into a pseudo-code to understand in a better way.

The value ten is assigned to the variable n.

For value = zero to less than a number.

Display the numbers.

Activity P.1: Implementing Pseudocode Based on the Algorithm for Calculating the Factorial of a Given Number

Algorithm for the Program Factorial of a Given Number.

Step 1: start

Step 2: initialize fact = 1

Step 3: input from the user value n

Step 4: for i=1 to i <= n repeat the process

Step 5: fact = fact * i

Step 6: i++ [increament i by one]

Step 7: print fact value

Step 8: stop

Now let’s implement pseudo-code from the above algorithm.


Start program

Declare fact and n

Enter number for n

for i=1 to i <=n

Perform fact = fact * i

Display fact

End program

Activity P.2: Implement Algorithm and Pseudocode to find the Maximum Number in an Array:

Algorithm:

Initialize a variable max with the first element of the array.

Iterate through the array.

If the current element is greater than max, update max.

After iterating through the array, max will contain the maximum element.

Pseudocode:

procedure findMax(arr)

max = arr[0]

for each element in arr from index 1

if element > max

max = element

return max

end procedure

Activity P.3: Implement Algorithm and Pseudocode to check if a Number is Prime:

Algorithm:

Read a number num.

Iterate from 2 to the square root of num.

If num is divisible by any number in this range, it is not prime.


If num is not divisible by any number, it is prime.

Pseudocode:

procedure isPrime(num)

if num <= 1

return false

for i from 2 to square root of num

if num % i == 0

return false

return true

end procedure

Designing Algorithms Using Recursion

Recursion refers to the technique of defining a process in terms of its own. It is used to solve complex programming
problems that are repetitive in nature.

The basic idea behind recursion is to break a problem into smaller versions of its own, and then, build a solution for the
entire problem. This may sound similar to the divide and conquer technique. However, the recursion technique is different
from the divide and conquer technique. Divide and conquer is a theoretical concept that may be implemented in a
computer program with the help of recursion.

Recursion is implemented in a program by using a recursive procedure or function. A recursive procedure is a function that
invokes itself.

Consider a function f(n), which is the sum of the first n natural numbers. This function can be defined in different ways.

In mathematics, the function will be defined as:

f(n) = 1 + 2 + 3 + 4 + 5 +...+ n

However, the same function can be defined in a recursive manner as:

f(n) = f(n – 1) + n

where, n > 1 and f(1) = 1

In this case, the recursive definition of the f(n) function calls the same function, but its arguments are reduced by one.
Recursion will end when n = 1. In that case, f(1) = 1.

To understand this concept, consider a factorial function. A factorial function is defined as:

n! = 1 × 2 × 3 × 4 × … × n

This same factorial function can be redefined in a recursive manner as:

n! = (n – 1)! × n
where, n > 1 and 0! = 1

This definition of n! is recursive because it refers to itself when it uses (n – 1)!. The value of n! is explicitly given when n = 0
and the value of n! for arbitrary n is defined in terms of the smaller value of n, which is closer to the base value, 0.

If you have to calculate 3! by using recursion, you first define 3! in terms of 2! as:

3!=(3×2!)

Now, you will define 2! in terms of 1! as:

3!=(3×(2×1!))

Now, you will define 1! in terms of 0! as:

3!=(3×(2×(1×0!)))

Now, 0! is defined as 1. Therefore, the expression becomes:

3!=(3×(2×(1×1)))

3!=(3×(2×1))

3!=(3×2)

3!=6

The recursive algorithm for determining the factorial of a number, n, can be written as:

Algorithm: Factorial(n)

1. If n = 0, then: // Terminating condition Return (1)

2. Return (n × Factorial(n – 1))

Please note that every recursive algorithm should have a terminating condition. Otherwise, the algorithm will keep on
calling itself infinitely.

The main advantage of recursion is that it is useful in writing clear, short, and simple programs. One of the most common
and interesting problems that can be solved by using recursion is the Tower of Hanoi problem.

Tower of Hanoi

Tower of Hanoi is a classical problem, which consists of n different sized disks and three pins over which these disks can be
mounted. All the disks are placed on the first pin with the largest disk at the bottom, and the remaining disks are placed in
decreasing order of their size, as shown in the following figure.
The Tower of Hanoi Problem

The objective of the game is to move all disks from the first pin to the third pin in the least number of moves by using the
second pin as intermediary.

To play this game, you need to follow the following rules:

• Only one disk can be moved at a time.

• A larger disk cannot be placed over a smaller one.

Let n be the number of the discs. If n = 3, it will require seven moves to transfer all discs from pin one to pin three, as
shown in the table.

The Sequence of Moves for n = 3

The moves given in the preceding table are illustrated in the following figure.
The Moves for Solving the Tower of Hanoi Problem

When n = 2, we first move the top disc from pin 1 to pin 2. Then, move the top disc from pin 1 to pin 3, and later, move the
top disc from pin 2 to pin 3.

The solution for n = 1 will be to move the disc from pin 1 to pin 3.

In general, to move n discs from pin 1 to pin 3 by using pin 2 as intermediary, you first need to move the top

n - 1 disc(s) from pin 1 to pin 2 by using pin 3 intermediary.

The following algorithm can be used to move the top n discs from the first pin, START, to the final pin,

FINISH, through the temporary pin, TEMP:

MOVE (n, START, TEMP, FINISH)

1. When n = 1:

a. MOVE a disc from START to FINISH


b. Return

2. Move the top n – 1 discs from START to TEMP using FINISH as an intermediary [MOVE (n – 1, START, FINISH,
TEMP)]

3. Move the top disc from START to FINISH

4. Move the top n – 1 discs from TEMP to FINISH using START as an intermediary [MOVE (n – 1, TEMP, START, FINISH)]

In general, this solution requires 2n - 1 moves for n discs.

Implementing Sorting Algorithms

Sorting Data

To retrieve the desired record, you need to sequentially traverse the list of names one by one because the names are not
sorted. This is a time-consuming activity. When you have to retrieve a record from a huge volume of data, the activity
becomes even more difficult.

A simple solution to this problem is sorting. Sorting is the process of arranging data in some predefined order or sequence.
The order can be either ascending or descending.

If the data is sorted, you can directly go to the section that stores the names starting with ‘S’, thereby reducing the number
of records to be traversed.

There are different types of sorting algorithms that can help you sort data in a particular order. These sorting algorithms
may provide varying efficiency levels. However, even when two algorithms have the same efficiency, there can be
situations when one works better than the other.

Selecting a Sorting Algorithm

Since there are various sorting algorithms, it becomes important to understand which sorting algorithm to use in a
particular situation. To select an appropriate algorithm, you need to consider the following criteria in the suggested order:

• Execution time

• Storage space

• Programming effort

Consider a situation where the data that needs to be sorted is small in quantity. To sort this data, all the sorting algorithms
will use a reasonable amount of storage space. In addition, the sorting algorithms will be executed in a reasonable amount
of time.

Therefore, in this situation, the criteria for selecting the sorting algorithm will be the programming effort involved. An
algorithm that requires less programming efforts will be preferred over an algorithm that requires more programming
efforts.

Consider another situation where the data that needs to be sorted is large in size. In such a situation, the time taken by
different algorithms may differ drastically because of the difference in their orders of growth. For example, when there are
a large number of elements, an algorithm with a logarithmic order of growth will execute faster than an algorithm with a
quadratic order of growth.

In addition, with an increase in data, the space requirement for different algorithms also differs drastically. Therefore,
when the data is large, you need to select a sorting algorithm that makes the most efficient use of time or memory,
depending upon the requirement.

Types of Sorting Algorithms

There are various sorting algorithms that are used to sort data. Some of these are:

• Bubble sort

• Insertion sort

• Quick sort

Let us discuss the working of these algorithms in detail.

Sorting Data by Using Bubble Sort

Bubble sort is one of the simplest sorting algorithms. This algorithm has a quadratic order of growth and is therefore
suitable for sorting small lists only. The algorithm works by repeatedly scanning through the list, comparing adjacent
elements, and swapping them if they are in the wrong order. The algorithm gets its name from the way smaller elements
"bubble" to the top of the list after being swapped with the greater elements.

Implementing the Bubble Sort Algorithm

To understand the implementation of the bubble sort algorithm, consider an unsorted list of numbers stored in an array.
Suppose there are n elements in the array, and the array needs to be sorted in ascending order.

To implement the bubble sort algorithm, you need to traverse the list multiple times. The process of traversing the entire
list once is called a pass. Sorting is performed in multiple passes.

Pass 1

In Pass 1, you compare the first two elements, and interchange their values if the first number is greater than the second
number. Then, you compare the second and third elements, and interchange their values if they are not in the correct
order. You repeat this process till the (n – 1)th element is compared with the nth element. The total number of
comparisons in Pass 1 is therefore, n – 1.

By the end of Pass 1, the largest element is placed at the nth position in the array. The number of comparisons is one less
than the total number of elements in the list.

Pass 2

In Pass 2, you repeat the same process as in Pass 1, but stop the comparison after comparing the element at the (n – 2)th
position with the element at the (n – 1)th position. This time, the number of comparisons required will be one less than
what is required in Pass 1. The total number of comparisons in Pass 2 is therefore, n – 2.
By the end of Pass 2, the second largest number will be placed at the (n – 1)th position in the array.

Pass 3

In Pass 3, you repeat the same process as in Pass 2, and this time, there will be n – 3 comparisons. This means that the
comparison will stop after comparing the element at the (n – 3)th position with the element at the (n – 2)th position. By
the end of Pass 3, the third largest number will be placed at the (n – 2)th position in the array.

Pass n – 1

Continuing the same process in all subsequent passes, in the (n – 1)th pass, you will have to perform only one comparison.
After the completion of this pass, the list will be sorted in ascending order.

Consider an example. You have an unsorted list containing the ranks of students based on the results of an examination.
You need to sort these ranks in the ascending order. The list of ranks is stored in an array, as shown in the following figure.

The List of Ranks in an Array

There are five elements in the list. Therefore, four passes will be required to sort the list.

In Pass 1, you need to perform the following steps:

1. Compare arr[0] with arr[1]. In the given list, arr[0] is greater than arr[1]. Therefore, you need to interchange the
two values. The resultant list is shown in the following figure.

The List of Ranks in an Array

2. Compare arr[1] with arr[2]. Here, arr[1] is less than arr[2]. Therefore, the values remain unchanged.

3. Compare arr[2] with arr[3]. Here, arr[2] is less than arr[3]. Therefore, the values remain unchanged.

4. Compare arr[3] with arr[4]. Here, arr[3] is greater than arr[4]. Therefore, you need to interchange the two values,
as shown in the following figure.
The List of Ranks in an Array

At the end of Pass 1, the largest element is placed at the last index position. The preceding process will be repeated in all
the subsequent passes. However, the value at index 4 will not be compared in any of the subsequent passes because it has
already been placed at its correct position.

In Pass 2, you need to perform the following steps:

1. Compare arr[0] with arr[1]. In the given list, arr[0] is less than arr[1]. Therefore, the values remain unchanged.

2. Compare arr[1] with arr[2]. Here, arr[1] is less than arr[2]. Therefore, the values remain unchanged.

3. Compare arr[2] with arr[3]. Here, arr[2] is greater than arr[3]. Therefore, you need to interchange the two values,
as shown in the following figure.

The List of Ranks in an Array

At the end of this pass, the second largest element is placed at its correct position in the list. You will repeat the same
process in the subsequent passes. However, the values at indexes, 3 and 4, will not be compared in the subsequent passes,
because they are already placed at their correct positions.

The following figure shows the result after Pass 3.

The List of Ranks in an Array

Similarly, the following figure shows the result after Pass 4.

The List of Ranks in an Array

After Pass 4, the list is completely sorted.

The following algorithm depicts the logic of bubble sort:

1. Set pass = 1.

2. Repeat step 3 varying j from 0 to n – 1 – pass.


3. If the element at index j is greater than the element at index j + 1, swap the two elements.

4. Increment pass by 1.

5. If pass <= n – 1 go to step 2.

Determining the Efficiency of the Bubble Sort Algorithm

The efficiency for a sorting algorithm is measured in terms of the number of comparisons. The number of comparisons in
bubble sort can be easily computed. In bubble sort, there are n – 1 comparisons in Pass 1,

n – 2 comparisons in Pass 2, and so on. Therefore, the total number of comparisons will be

(n – 1) + (n – 2) + (n – 3) + … + 3 + 2 + 1, which is an arithmetic progression.

The formula for determining the sum of an arithmetic progression is:

Sum = n/2 [2a + (n – 1)d]

where,

n is the total number of elements in the arithmetic progression.

a is the first element in the arithmetic progression.

d is the step value, which is the difference in successive terms of an arithmetic progression.

For the preceding arithmetic progression:

n=n–1

a=1

d=1

Therefore,

Sum = (n – 1)/2 [2 × 1 + (n – 1 – 1) × 1]

Sum = (n – 1)/2 [2 + (n – 2)]

Sum = (n – 1)/2 (n)

Sum = n(n – 1)/2

n(n – 1)/2 is of the O(n2) order. Therefore, the bubble sort algorithm is of the order, O(n2). This means that the time taken
to execute the algorithm increases quadratically with an increase in the size of the list.

Suppose, it takes 100 ns to execute the algorithm on a list of 10 elements. Now, if the number of elements is doubled, that
is, the number of elements is increased to 20, the execution time will increase to 400 ns, which is four times the time taken
to execute the algorithm on 10 elements.

Activity 2.1: Sorting Data by Using the Bubble Sort Algorithm

Problem Statement
Write a program to store the marks of 10 students in an array. Include a function to sort the elements of the array by using
the bubble sort algorithm. After sorting the array, display the sorted list.

Solution in Java

To sort the marks of students by using the bubble sort algorithm, you need to perform the following tasks:

1. Write the code in NetBeans.

2. Execute the code and verify the output.

Task 1: Writing the Code in NetBeans

To write the code in NetBeans, you need to perform the following steps:

1. Open NetBeans.

2. Create a new project with the name, ListBubbleSort.

3. Replace the code given in the ListBubbleSort.java file with the following code:

package listbubblesort;

import java.util.*;

public class ListBubbleSort {

// Array of integers to hold values

private int[] a = new int[20];

// Number of elements in array

private int n;

// Function to accept array elements

public void read()

// Get the number of elements to store in the array

while (true)

System.out.print("Enter the number of elements in the array: ");

String s = new Scanner(System.in).nextLine();

n = Integer.parseInt(s);

if (n > 0 && n <= 20)

{
break;

else if (n > 20)

System.out.println("\nArray can have maximum 20 elements.\n");


}

else if (n < 0)

System.out.println("\nEnter positive number.\n");

System.out.println("");

System.out.println("-----------------------");

System.out.println(" Enter array elements ");

System.out.println("-----------------------");

// User inputs for the array

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

System.out.print("<" + (i + 1) + "> ");

String s1 = new Scanner(System.in).nextLine();

a[i] = Integer.parseInt(s1);

public void display()

// Display the sorted array

System.out.println("");

System.out.println("-----------------------");

System.out.println(" Sorted array elements ");

System.out.println("-----------------------");
for (int j = 0; j < n; j++)

System.out.println(a[j]);

// Function to sort using Bubble sort

public void BubbleSort()

for (int i = 1; i < n; i++) // For n – 1 passes

// In pass i, compare the first n – i elements

// with their next elements

for (int j = 0; j < n - i; j++)

if (a[j] > a[j + 1]) {

/* If the elements are not in the correct order,

swap the elements*/

int temp;

temp = a[j];

a[j] = a[j + 1];

a[j + 1] = temp;

}
public static void main(String[] arg)

// Creating the object of the BubbleSort class

ListBubbleSort myList = new ListBubbleSort();

// Function call to accept array elements


myList.read();

// Function call to sort array

myList.BubbleSort();

// Function call to display the sorted array

myList.display();

// To exit from the console

System.out.println("\n\nPress Enter to exit.");

new Scanner(System.in).nextLine();

4. Save the project.

Task 2: Executing the Code and Verify the Output

Sorting Data by Using Insertion Sort

Similar to bubble sort that has a quadratic order of growth, insertion sort also has a quadratic order of growth, and is,
therefore, used for sorting small lists only.

However, if the list that needs to be sorted is nearly sorted, insertion sort becomes more efficient than bubble sort. This is
because bubble sort always performs the same number of comparisons, no matter what is the initial ordering of elements.
In contrast, insertion sort performs a different number of comparisons depending on the initial ordering of elements.
When the elements are already in the sorted order, insertion sort needs to make few comparisons.

Implementing the Insertion Sort Algorithm

The insertion sort algorithm divides the list into two parts, sorted and unsorted. Initially, the sorted part contains only one
element. In each pass, one element from the unsorted list is inserted at its correct position in the sorted list. As a result,
the sorted list grows by one element and the unsorted list shrinks by one element in each pass.

Consider the following figure that shows an unsorted list stored in an array that needs to be sorted in ascending order by
using the insertion sort algorithm.

The Unsorted List of Elements


To sort this list by using the insertion sort algorithm, you need to divide the list into two sub lists, sorted and unsorted.
Initially, the sorted list contains only the first element and the unsorted list contains the remaining four elements, as
shown in the following figure.

The List Divided in Two Parts

Now, to further sort the unsorted list, you need to perform a number of passes:

1. In Pass 1, take the first element, 80, from the unsorted list, and store it at its correct position in the sorted list.
Here, 80 is greater than 70, therefore, it remains at array index 1. However, array index 1 is now considered to be a part of
the sorted list. This is shown in the following figure.

The Element Stored at its Correct Position in the Sorted List

The sorted list has two elements now, and the unsorted list has three elements.

2. In Pass 2, take the first element, 30, from the unsorted list, and store it at its correct position in the sorted list.
Here, 30 is less than 70 and 80, therefore, it needs to be stored at array index 0. To store 30 at array index 0, 80 needs to
be shifted to array index 2, and 70 needs to be shifted to array index 1. This is shown in the following figure.

The Element Stored at its Correct Position in the Sorted List

The sorted list has three elements now, and the unsorted list has two elements.

3. In Pass 3, take the first element, 10, from the unsorted list, and store it at its correct position in the sorted list.
Here, 10 is smaller than the three elements in the sorted list. Therefore, it needs to be stored at array index 0. To store 10
at array index 0, 30 needs to be shifted to array index 1, 70 needs to be shifted to array index 2, and 80 needs to be shifted
to array index 3. This is shown in the following figure.

The Element Stored at its Correct Position in the Sorted List

The sorted list has four elements now, and the unsorted list has one element.

4. In Pass 4, take the first element, 20, from the unsorted list, and store it at its correct position in the sorted list.
Here, 20 is greater than 10 and smaller than the other three elements in the sorted list. Therefore, it needs to be stored at
array index 1.

To store 20 at array index 1, 30 needs to be shifted to array index 2, 70 needs to be shifted to array index 3, and 80 needs
to be shifted to array index 4. This is shown in the following figure.

The Element Stored at its Correct Position in the Sorted List

The unsorted list is now empty, and the sorted list contains all the elements. This means that the list is now completely
sorted.

The following algorithm depicts the logic of insertion sort:

1. Repeat steps 2, 3, 4, and 5 varying i from 1 to n – 1.

2. Set temp = arr[i].

3. Set j = i – 1.

4. Repeat until j becomes less than 0 or arr[j] becomes less than or equal to temp:

a. Shift the value at index j to index j + 1.

b. Decrement j by 1.

5. Store temp at index j + 1.

Determining the Efficiency of the Insertion Sort Algorithm

Consider an unsorted list of numbers with n elements. To sort this unsorted list by using insertion sort, you need to
perform (n – 1) passes. In insertion sort, if the list is already sorted, you will have to make only one comparison in each
pass.
In n – 1 passes, you need to make n – 1 comparisons. This is the best case for insertion sort. Therefore, the best case
efficiency of insertion sort is of the order, O(n).

Now, consider a situation where initially, the list is stored in the reverse order. In this case, you need to make one
comparison in Pass 1, two comparisons in Pass 2, three comparisons in Pass 3, and n – 1 comparisons in the (n – 1)th pass.

The formula for determining the total number of comparisons in this case is:

Sum = 1 + 2 + . . . + (n – 1)

This is the same as bubble sort. Therefore, the worst case efficiency of insertion sort is of the order, O(n2).

Activity 3: Sorting Data by Using the Insertion Sort Algorithm

Problem Statement

Write a program that stores 10 numbers in an array, and sorts them by using the insertion sort algorithm.

Solution in Java

To sort a list of numbers by using the insertion sort algorithm, you need to perform the following tasks:

1. Write the code in NetBeans.

2. Execute the code and verify the output.

Task 1: Writing the Code in NetBeans

To write the code in NetBeans, you need to perform the following steps:

1. Open NetBeans.

2. Create a new project with the name, ListInsertionSort.

3. Replace the code given in the ListInsertionSort.java file with the following code:

package listinsertionsort;

import java.util.*;

public class ListInsertionSort {

// Array of integers to hold values

private int[] a = new int[20];

// Number of elements in array

private int n;

// Function to accept array elements

public void read()


{

// Get the number of elements to store in the array

while (true)

System.out.print("Enter the number of elements in the array: ");

String s = new Scanner(System.in).nextLine();

n = Integer.parseInt(s);

if (n > 0 && n <= 20)

break;

else if (n > 20)

System.out.println("\nArray can have maximum 20 elements.\n");

else if (n < 0)

System.out.println("\nEnter positive number.\n");


}

System.out.println("");

System.out.println("-----------------------");

System.out.println(" Enter array elements ");

System.out.println("-----------------------");

// User inputs for the array

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

System.out.print("<" + (i + 1) + "> ");

String s1 = new Scanner(System.in).nextLine();

a[i] = Integer.parseInt(s1);
}

public void display()

// Display the sorted array

System.out.println("");

System.out.println("-----------------------");

System.out.println(" Sorted array elements ");

System.out.println("-----------------------");

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

System.out.println(a[j]);

// Function to sort using Insertion sort

public void InsertionSort()

for (int i = 1; i <= n - 1; i++)

int temp = a[i];

int j = i - 1;

while ((j >= 0) && (a[j] > temp))

a[j + 1] = a[j];

j = j - 1;

a[j + 1] = temp;

public static void main(String[] args) {


// Creating the object of the InsertionSort class

ListInsertionSort myList = new ListInsertionSort();

// Function call to accept array elements

myList.read();

// Function call to sort array

myList.InsertionSort();

// Function call to display the sorted array

myList.display();

// To exit from the console

System.out.println("\n\nPress the Enter key to exit.");

System.exit(0);

4. Save the project.

Task 2: Executing the Code and Verify the Output.

Sorting Data by Using Quick Sort

Various sorting algorithms, such as bubble sort and insertion sort, are useful for sorting small lists of data.

However, for larger lists, these algorithms prove inefficient.

Quick sort is one of the most efficient sorting algorithms useful for sorting large lists. This algorithm involves successively
dividing the problem into smaller problems, until the problems become so small that they can be easily solved. The
solutions to all the smaller problems are then combined to solve the complete problem.

Implementing the Quick Sort Algorithm

The quick sort algorithm works by selecting an element from the list called a pivot, and then partitioning the list into two
parts that may or may not be equal. The list is partitioned by rearranging the elements in such a way that all the elements
towards the left end of the list are smaller than the pivot, and all the elements towards the right end of the list are greater
than the pivot. The pivot is then placed at its correct position between the two sub lists.

This process is repeated for each of the two sub lists created after partitioning, and the process continues until one
element is left in each sub list.

To understand the concept behind the quick sort algorithm, consider an unsorted list of numbers stored in an array named
arr, as shown in the following figure.
The Unsorted List

You need to sort the preceding list in ascending order by using the quick sort algorithm.

In the given list, you can take arr[0] as the pivot, as shown in the following figure.

The Selection of the Pivot Value

After selecting the pivot value, you need to perform the following steps:

1. Starting from the left end of the list (at index 1) and moving in the left-to-right direction, search the first element
that is greater than the pivot value. Here, arr[1] is the first value greater than the pivot.

2. Similarly, starting from the right end of the list and moving in the right-to-left direction, search the first element
that is smaller than or equal to the pivot value. Here, arr[4] is the first value smaller than pivot. The two searched values
are depicted in the following figure.

The Array Depicting the Two Searched Values

In the preceding figure, the greater value is on the left hand side of the smaller value. This means that the values are not in
the correct order.

3. Interchange arr[1] with arr[4] so that the smaller value is placed on the left hand side and the greater value is
placed on the right hand side. The resultant array is shown in the following figure.

The Array after Interchanged Values


4. Starting from arr[2] and moving in the left-to-right direction, continue the search for an element greater than the
pivot. Here, arr[2] is found to be greater than the pivot.

5. Similarly, starting from arr[3] and moving in the right-to-left direction, continue the search for an element smaller
than or equal to the pivot. Here, arr[1] is found to be smaller than the pivot. The two searched values are depicted in the
following figure.

The Array Depicting the Two Searched Values

In the preceding figure, the smaller value is on the left hand side of the greater value. This indicates that the values are in
the right order. Therefore, the values need not be interchanged and the search stops here. At this stage, the list can be
divided into two sub lists, List 1 and List 2.

List 1 contains all the values less than or equal to the pivot, and List 2 contains all the values greater than the pivot, as
shown in the following figure.

The Two Sub Lists

6. Interchange the pivot value with the last element of List 1, as shown in the following figure.

Interchanging the Pivot Value


The pivot value, 28, is now placed at its correct position in the list. All the elements towards the right side of 28 are greater
than 28, and all the elements towards the left side of 28 are smaller than or equal to 28. Now the two sub lists, List 1 and
List 2, need to be sorted.

7. Truncate the last element, that is, pivot from List 1 because it has already reached its correct position. List 1 now
has only one element. Therefore, nothing needs to be done to sort it.

8. Sort the second list, List 2 by following the same process as for the original list. The pivot in this case will be arr[2],
that is, 46, as shown in the following figure.

The Pivot Value for List 2

9. Starting from the left, arr[4] is greater than the pivot value. Similarly, starting from the right, arr[7] is smaller than
the pivot value. The greater value is on the left hand side of the smaller value. This means that the values are not in the
correct order.

Therefore, interchange the two values, as shown in the following figure.

The Interchanged Values

10. Starting from arr[5] and moving in the left-to-right direction, continue the search for an element greater than the
pivot. Here, arr[5] is found to be greater than the pivot.

11. Similarly, starting from arr[6] and moving in the right-to-left direction, continue the search for an element smaller
than or equal to the pivot. Here, arr[4] is found to be smaller than the pivot. The two searched values are depicted in the
following figure.
The Values Greater and Smaller than the Pivot

In the preceding figure, the smaller value is to the left hand side of the greater value. This indicates that the values are in
the right order. Therefore, the values need not be interchanged and the search stops here. At this stage, the list can be
divided into two sub lists, Sublist 1 and Sublist 2, in such a way that Sublist 1 contains all the values less than or equal to
the pivot, and Sublist 2 contains all the values greater than the pivot, as shown in the following figure.

The List 2 Divided into Two Sub Lists

12. Interchange the pivot value with the last element of Sublist 1, as shown in the following figure.

The Interchange of the Pivot Value

The pivot value, 46, has now reached its correct position in the list. Now, you need to sort Sublist 1 and Sublist 2.

13. Sort Sublist 1 and Sublist 2 by following the same procedure:

a. Select a pivot value.

b. Divide the sub list into two parts in a way that one part contains all elements less than or equal to the pivot, and
the other part contains all elements greater than the pivot.

c. Place the pivot at its correct position between the two parts of the list.

d. The preceding process will stop when there is a maximum of one element in each sub list. At that point, the list will
be completely sorted, as shown in the following figure.

The Sorted List

The quick sort algorithm recursively divides the list into two sub lists. Therefore, the algorithm for quick sort is recursive in
nature. The following algorithm depicts the logic of quick sort:

Algorithm: QuickSort(low, high) // Low is the index of the first element in

//the list and high is the index of the last //element in the list
1. If (low > high): Return.

2. Set pivot = arr[low].

3. Set i = low + 1.

4. Set j = high.

5. Repeat step 6 until i > high or arr[i] > pivot. //Search for an element //greater than the pivot

6. Increment i by 1.

7. Repeat step 8 until j < low or arr[j] < pivot. // Search for an element

// smaller than the pivot

8. Decrement j by 1.

9. If i < j: /* If greater element is on the left of smaller element Swap arr[i] with arr[j].

10. If i <= j:

Go to step 5. // Continue the search

11. If low < j:

Swap arr[low] with arr[j]. // Swap the pivot with last element in

// the first part of the list

12. QuickSort(low, j – 1). /* Apply quick sort on the list to the left of the pivot */

13. QuickSort(j + 1, high). /* Apply quick sort on the list to the right of the pivot */

Activity 4: Sorting Data by Using the Quick Sort Algorithm

Problem Statement

Write a program that stores 10 numbers in an array and sorts them by using the quick sort algorithm. In addition, the
program should also calculate the number of comparisons and the number of data movements.

Solution in Java

To sort the list of numbers by using the quick sort algorithm, you need to perform the following tasks:

1. Write the code in NetBeans.

2. Execute the code and verify the output.

Task 1: Writing the Code in NetBeans

To write the code in NetBeans, you need to perform the following steps:

1. Open NetBeans.

2. Create a new project with the name, ListQuickSort.


3. Replace the code given in the ListQuickSort.java file with the following code:

package listquicksort;

import java.util.*;

public class ListQuickSort {

// Array of integers to hold values

private int[] arr = new int[20];

private int cmp_count; // Number of comparisons

private int mov_count; // Number of data movements

// Number of elements in array

private int n;

public ListQuickSort()

cmp_count = 0;

mov_count = 0;

private void read()

while (true)

System.out.print("Enter the number of elements in the array: ");

String s = new Scanner(System.in).nextLine();

n = Integer.parseInt(s);

if (n > 0 && n <= 20)

break;

else if (n > 20)

System.out.println("\nArray can have maximum 20 elements.\n");

}
else if (n < 0)

System.out.println("\nEnter positive number.\n");

System.out.println("\n-----------------------");

System.out.println(" Enter array elements ");

System.out.println("-----------------------");

// Get array elements

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

System.out.print("<" + (i + 1) + "> ");

String s1 = new Scanner(System.in).nextLine();

arr[i] = Integer.parseInt(s1);

private void swap(int x, int y) /* Swaps the element at index x with

the element at index y */

int temp;

temp = arr[x];

arr[x] = arr[y];

arr[y] = temp;

public void q_sort(int low, int high)

int pivot, i, j;

if (low > high)

return;
}

/* Partition the list into two parts:

One containing elements less than or equal to pivot

Other containing elements greater than pivot */

i = low + 1;

j = high;

pivot = arr[low];

while (i <= j)

// Search for an element greater than pivot

while ((arr[i] <= pivot) && (i <= high))

i++;

cmp_count++;

cmp_count++;

// Search for an element less than or equal to pivot

while ((arr[j] > pivot) && (j >= low))

j--;

cmp_count++;

cmp_count++;

if (i < j) /* If the greater element is on the

left of the smaller element */

/* Swap the element at index i with the element

at index j */

swap(i, j);

mov_count++;
}

/* j now contains the index of the last element in the

sorted list. */

if (low < j)

swap(low, j); /* Move the pivot to its correct

position in the list */

mov_count++;

//Sort the list on the left of pivot using quick sort

q_sort(low, j - 1);

//Sort the list on the right of pivot using quick sort

q_sort(j + 1, high);

private void display()

System.out.println("\n-----------------------");

System.out.println(" Sorted array elements ");

System.out.println("-----------------------");

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

System.out.println(arr[j]);

System.out.println("Number of comparisons: " + cmp_count);

System.out.print("Number of data movements: " + mov_count);

private int getSize()

return (n);
}

public static void main(String[] args)

{
// Declaring the object of the class

ListQuickSort myList = new ListQuickSort();

// Accept array elements

myList.read();

// Calling the sorting function

// First call to Quick Sort Algorithm

myList.q_sort(0, myList.getSize() - 1);

// Display sorted array

myList.display();

// To exit from the console

System.exit(0);

4. Save the project.

Task 2: Executing the Code and Verify the Output.

Implementing Searching Algorithms

Performing Linear Search

Linear search is the simplest searching method that can be applied to a given collection of data. Given a list of items and an
item to be searched in the list, linear search will compare the item sequentially with the elements in the list.

Because the elements in the list are compared sequentially with the item to be searched, this type of search is also known
as sequential search.

Implementing Linear Search

To understand the implementation of the linear search algorithm, consider an example where you need to search the
record of an employee, whose employee ID is 1420, from a list of employee records. Linear search will begin by comparing
the required employee ID with the first element in the list.

If the values do not match, the employee ID will be compared with the second element. Again, if the values do not match,
the employee ID will be compared with the third element. This process will continue until the desired employee ID is found
or the end of the list is reached.

The following algorithm depicts the logic to search an employee ID in an array by using linear search:
1. Read the employee ID to be searched.

2. Set i = 0.

3. Repeat step 4 until i = n or arr[i] = employee ID.

4. Increment i by 1.

5. If i = n:

Display “Not Found”. Else:

Display “Found”.

Determining the Efficiency of Linear Search

The efficiency of a searching algorithm is determined by the running time of the algorithm. This running time is
proportional to the number of comparisons made for searching a record in a given list of records.

While performing linear search, if the desired record is found at the first position in the list, you will have to make only one
comparison. Therefore, the best case efficiency of linear search is O(1).

However, if the desired record is stored at the last position in the list or does not exist in the list, you will have to make n
comparisons, where n is the number of records in the list. Therefore, the worst case efficiency of linear search is O(n).

The average number of comparisons for a linear search can be determined by finding the average of the number of
comparisons in the best and the worst cases. This turns out to be (n+1)/2.

Suppose, you have to search for an element in an array of size 15. In the best case, you will find the element in only one
comparison. However, in the worst case, you will either find the element or conclude that the element does not exist in
the list after 15 comparisons. Therefore, in an average case, the element will be found in ((15+1)/2), that is, 8 comparisons.

Activity 5 Performing Linear Search

Problem Statement

Write a program to search a given number in an array that contains a maximum of 20 numbers by using the linear search
algorithm. If there are more than one occurrences of the element to be searched, then the program should display the
position of the first occurrence. The program should also display the total number of comparisons made.

Solution in Java

To implement the linear search algorithm, you need to perform the following tasks:

1. Write the code in NetBeans.

2. Execute the code and verify the output.

Task 1: Writing the Code in NetBeans

To write the code in NetBeans, you need to perform the following steps:

1. Open NetBeans.
2. Create a new project with the name, LinearSearch.

3. Replace the code given in the LinearSearch.java file with the following code:

package linearsearch;

import java.util.*;

public class LinearSearch

public static void main(String[] args)

int[] arr = new int[20]; // Array to be searched

int n; // Number of elements in the array

int i;

// Get the number of elements to store in the array

while (true)

System.out.print("Enter the number of elements in the array: ");

String s = new Scanner(System.in).nextLine();

n = Integer.parseInt(s);

if ((n > 0) && (n <= 20))

break;

else

System.out.println("\nArray should have minimum 1 and maximum 20

elements.\n");

// Accept array elements

System.out.println("");

System.out.println("-----------------------");
System.out.println(" Enter array elements ");

System.out.println("-----------------------");

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

System.out.print("<" + (i + 1) + "> ");

String s1 = new Scanner(System.in).nextLine();

arr[i] = Integer.parseInt(s1);

char ch; // Variable to store the choice(y/n) for continuing search

int ctr; // Variable to count the number of comparisons

do

// Accept the number to be searched

System.out.print("\nEnter the element you want to search: ");

int item = Integer.parseInt(new Scanner(System.in).nextLine());

// Apply linear search

ctr = 0;

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

ctr++;

if (arr[i] == item)

System.out.println("\n" + (new Integer(item)).toString() + " found

at position " + (new Integer(i + 1)).toString());

break;

if (i == n)

System.out.println("\n" + (new Integer(item)).toString() + " not


found in the array");

System.out.println("\nNumber of comparisons: " + ctr);

System.out.print("\nContinue search (y/n):");

ch = new Scanner(System.in).nextLine().charAt(0);

} while ((ch == 'y') || (ch == 'Y'));

4. Save the project

Task 2: Executing the Code and Verify the Output

Performing Binary Search

Linear search is a useful technique for searching small lists. However, it is an inefficient searching solution for large lists.
Suppose you have a list of 10,000 elements, and the item to be searched is the last item in the list. In this case, you will
have to make 10,000 comparisons to search the element. Therefore, linear search is not an appropriate technique for
searching large lists.

An alternate solution, which offers better efficiency for large lists, is binary search algorithm. This searching algorithm
helps you to search data in few comparisons. To apply binary search algorithm, you should ensure that the list to be
searched is sorted. If the list to be searched is not sorted, it needs to be sorted before binary search can be applied to it.

Implementing Binary Search

Consider an example where you have to search the name, Steve, in a telephone directory that is sorted alphabetically. In
this case, you do not search the name sequentially. Instead, you open the telephone directory at the middle to determine
the half portion that contains the name.

You can open that half portion at the middle to determine the quarter of the directory that contains the name. You repeat
the process until the required name is found. In this way, each time, you reduce the number of pages to be searched by
half, and thus, find the required name quickly.

The binary search algorithm is based on the preceding approach for searching an item in a sorted list.

Consider another example. Suppose you have nine items in a sorted array, as shown in the following figure.
The Sorted Array

In the preceding list, you have to search the element, 13, by using the binary search algorithm. To search this element, you
need to perform the following sequence of steps:

1. Compare the element to be searched with the middlemost element of the list. You can determine the index of the
middlemost element with the help of the following formula:

Mid = (Lower bound + Upper bound)/2

Here, Lower bound (LB) is the index of the first element in the list and Upper bound (UB) is the index of the last element in
the list.

In the preceding list, LB is 0 and UB is 8. Therefore,

Mid = (LB + UB)/2

Mid = (0 + 8)/2

Mid = 4

After determining the middlemost element, you need to check the following possible conditions:

Element to be searched = middle element: If this condition holds true, the desired element is found at arr[mid].

Element to be searched < middle element: If this condition holds true, you need to search the item towards the left of the
middle element.

Element to be searched > middle element: If this condition holds true, you need to search the element towards the right
of the middle element.

In the preceding example, the middle element is at the index, 4, as shown in the following figure.

The Middle Element

2. The element at the index, 4, is 25, which is greater than 13. Therefore, you will search the element towards the left
of the middle element, that is, in the list arr[0] to arr[3]. Here, LB is 0 and UB is equal to mid – 1, which is 3, as shown in
the following figure.
The LB and UB Pointers

3. Again, determine the index of the middlemost element of the list arr[0] to arr[3].

Mid = (LB + UB)/2

Mid = (0 + 3)/2

Mid = 1

Therefore, the middle element will be at the index, 1, as shown in the following figure.

The Middle Element

The element at the index, 1, is 13, which is the desired element. Therefore, the element is found at the index, 1, in the
preceding list.

The following algorithm depicts the logic to search a desired element by using binary search:

1. Accept the element to be searched.

2. Set lowerbound = 0.

3. Set upperbound = n – 1.

4. Set mid = (lowerbound + upperbound)/2.

5. If arr[mid] = desired element:

a) Display “Found”.

b) Go to step 10.

6. If desired element < arr[mid]: Set upperbound = mid – 1.

7. If desired element > arr[mid]: Set lowerbound = mid + 1.

8. If lowerbound <= upperbound: Go to step 4.

9. Display “Not Found”.

10. Exit.
Determining the Efficiency of Binary Search

In the binary search algorithm, with every step, the search area is reduced to half. Therefore, it requires few comparisons.

The best case for this algorithm will be the one where the element to be searched is present at the middlemost position in
the array. In this case, the desired element is found in just one comparison, and therefore, the efficiency of the search
process is O(1).

However, the worst case will be the one where the desired element is not found in the array. In this case, the process of
dividing the list into sub lists continues until there is only one item left for the comparison.

After bisecting the list, the following conditions can be there for the worst case:

• After the first bisection, the search space is reduced to n/2 elements, where n is the number of elements
in the original list.

• After the second bisection, the search space is reduced by n/4, that is, to n/22 elements.

• After the ith bisection, the search space is reduced to n/2i elements.

Suppose, after the ith bisection, the search space is reduced to one element. In this case,

n/2i = 1

n = 2i

i = log2n (If a = 2b, then b = log2a)

This means that the list can be bisected in maximum log2n times. After each bisection, only one comparison is made.
Therefore, the total number of comparisons will be log2n. This means that the worst case efficiency of binary search is
O(log n).

Activity 5.2: Performing Binary Search

Problem Statement

Write a program to search a number in an array that contains a maximum of 20 elements by using binary search. Assume
that the array elements are entered in the ascending order and all the array elements are unique. The program should also
display the total number of comparisons made.

Solution in Java

To implement the binary search algorithm, you need to perform the following tasks:

1. Write the code in NetBeans.

2. Execute the code and verify the output.

Task 1: Writing the Code in NetBeans

To write the code in NetBeans, you need to perform the following steps:

1. Open NetBeans.

2. Create a new project with the name, BinarySearch.


3. Replace the code given in the BinarySearch.java file with the following code:

package binarysearch;

import java.util.*;

public class BinarySearch

public static void main(String[] args)

int[] arr = new int[20]; // Array to be searched

int n; // Number of elements in the array

int i;

// Get the number of elements to store in the array

while (true)

System.out.print("Enter the number of elements in the array: ");

String s = new Scanner(System.in).nextLine();

n = Integer.parseInt(s);

if ((n > 1) && (n <= 20))

break;

else

System.out.println("\nArray should have minimum 1 and maximum 20

elements.\n");

// Accept array elements

System.out.println("");

System.out.println("--------------------------------");

System.out.println(" Enter array elements in ascending order ");


System.out.println("--------------------------------");

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

System.out.print("<" + (i + 1) + "> ");

String s1 = new Scanner(System.in).nextLine();

arr[i] = Integer.parseInt(s1);

char ch; // Variable to store the choice(y/n) for continuing search

do

// Accept the number to be searched

System.out.print("\nEnter the element you want to search: ");

int item = Integer.parseInt(new Scanner(System.in).nextLine());

// Apply binary search

int lowerbound = 0;

int upperbound = n - 1;

// Obtain the index of the middlemost element

int mid = (lowerbound + upperbound) / 2;

int ctr = 1; // Variable to count the number of comparisons

while ((item != arr[mid]) && (lowerbound <= upperbound)) /* Loop to

search for the element in the array */

if (item > arr[mid])

lowerbound = mid + 1;

else

upperbound = mid - 1;

}
mid = (lowerbound + upperbound) / 2;

ctr++;

if (item == arr[mid])

System.out.println("\n" + (new Integer(item)).toString() + " found

at position " + (new Integer(mid + 1)).toString());

else

System.out.println("\n" + (new Integer(item)).toString() + " not

found in the array\n");

System.out.println("\nNumber of comparisons: " + ctr);

System.out.print("\nContinue search (y/n):");

ch = new Scanner(System.in).nextLine().charAt(0);
} while ((ch == 'y') || (ch == 'Y'));

4. Save the project.

Task 2: Executing the Code and Verifying the Output

Solving Programming Problems Using Linked Lists

Introduction to Linked Lists

Consider a scenario where you have to write a program to generate and store all the prime numbers between one and a
million, and then display them. If you use an array to store the prime numbers, the size of the array needs to be declared
in advance. However, the number of prime numbers between 1 and 10,00,000 is not known in advance. Therefore, to store
all the prime numbers, you need to declare an arbitrarily large size array. In the worst case, you may need to declare an
array of the size, 10,00,000.

For instance, you declare an array of the size, n. Now, if the number of prime numbers between 1 and 10,00,000 is more
than n, all the prime numbers cannot be stored. Similarly, if the number of prime numbers is less than n, a lot of memory
space will be wasted.
Therefore, array is not a suitable choice for storing the numbers. What can you do in such a situation?

To solve such problems, you can use a dynamic data structure that does not require you to specify the size in advance and
allows memory to be allocated whenever required. An example of such a data structure is a linked list.

Linked lists are flexible data structures that provide a convenient way to store data. You do not have to specify the size of
the list in advance. Memory is allocated dynamically whenever required. Linked lists are useful in operations where
frequent manipulation (insertion and deletion) of data is required. There are various types of linked lists. Each has a unique
feature. The choice of a particular type of linked list is based on the problem.

Defining Linked Lists

A linked list is a chain of elements in which each element consists of data, as well as a link to the next element. The link
stores the address of the next logically similar element in the list. Each such element of a linked list is called a node, as
shown in the following figure.

The Structure of a Node

Through the address field, one node logically references another node of the same type in the linked list. Due to this
property, a linked list is called a self-referential data structure. The structure of a linked list is shown in the following figure.

The Structure of a Linked List

Each node in a linked list contains the address of the next node in the list. However, there is no node that contains the
address of the first node. To keep track of the first node of the list, a variable is used that stores the address of the first
node of the list. In the preceding example, a variable named START is used to store the address of the first node of the list.
When the list does not contain any node, START is set to the value, NULL.

The last node does not need to point to any other node. Therefore, the content of the address field of the last node is set
to NULL, so that the end of the list can be identified.
Identifying Different Types of Linked Lists

Based on ways the various nodes are connected to each other, linked lists can be of the following types:

Singly-linked list: It is the simplest type of linked list where each node points to the next node. The last node does not
point to any other node in the list. Therefore, it points to NULL. This means that a node pointing to NULL refers to the end
of the list. The structure of a singly-linked list is shown in the following figure.

The Singly-Linked List

Doubly-linked list: In this type of linked list, each node contains a reference to the next node, as well as the previous node.
Therefore, in a doubly-linked list, it is possible to traverse in the reverse direction also, which is not possible in a singly-
linked list. The structure of a doubly-linked list is shown in the following figure.

The Doubly-Linked List

Circular-linked list: It is similar to a singly-linked list where each node points to the next node in the list. The difference lies
with the last node where the last node points to the first node instead of pointing to NULL. Therefore, circular-linked list
neither has any beginning nor has any end. The structure of the circular-linked list is shown in the following figure.

The Circular-Linked List

In a circular-linked list, usually a variable is used to store the address of the last node of the list. The address of the first
node of the list can be obtained from the last node of the list as the last node points to the first node.

Implementing Singly-Linked Lists


A singly-linked list is a simple data structure that acts as a base for the various other data structures. It provides a
convenient way to organize and represent data for efficient manipulation in computer applications.

The various operations implemented on a linked list are insert, delete, traverse, and search.

Representing a Singly-Linked List

A singly-linked list is represented in a program by defining the following classes:

A class that represents a node in a linked list: A node is the basic building block in a linked list. To implement a linked list
in a computer program, you can create a class named Node that represents a node in a linked list. This class contains the
data members of varying data types, which represent the data to be stored in the linked list. In addition to this, it also
contains the reference of the class type, Node, to hold the reference of the next node in the sequence. Consider the
following declaration of the Node class in Java:

// Code in Java

class Node

public int info;

public Node next; // Variable containing the address of the // next node in the sequence

In the preceding Node class declaration, the node contains only one data element, that is, an integer.

However, a node can also contain multiple data elements.

For example, to store the details of the students in a class, you can declare the Node class that contains the details of a
student, such as name, roll number, and marks. Consider the following declaration of a class named Node in Java:

// Code in Java

class Node

public int roll_no;

public String name;

public float marks;

public Node next;

}
A class that represents a linked list: This class consists of a set of operations, which are implemented on a linked list.
These operations are insertion, deletion, search, and traversal. It also contains the declaration of the variable/pointer,
START, which always points to the first node in the list. When the list is empty, START points to NULL. Consider the
following Java declarations of the class named List that implement the various operations on a linked list:

// Code in Java

class List

private Node START;

List()

START = null;

public void addNode(int element)

/* statements */

public bool search(int element)

/* statements */

public bool delNode(int element)

/* statements */

public void traverse()

/* statements */

}
Inserting a Node in a Singly-Linked List

Insertion in a singly-linked list refers to the process of adding a new node in the list or creating a new linked list if it does
not exist. Therefore, to insert a node in a linked list, you need to first check whether the list is empty or not.

If the linked list is empty, the following algorithm depicts the logic to insert a node in the linked list:

1. Allocate memory for the new node.

2. Assign a value to the data field of the new node.

3. Make the next field of the new node point to NULL.

4. Make START point to the new node.

The process of inserting a node in an empty list is illustrated in the following table.

Operation Illustration

Allocate memory for a new node.

Assign the value to the data field of the new


node.

Make the next field of the new node point to


NULL.

Make START point to the new node.

The Insert Operation in an Empty Singly-Linked List

If the linked list is not empty, you may need to insert a node at any of the following positions in the list:

• Beginning of the list

• End of the list

• Between two nodes in the list

The place where you insert an element in the list would depend on the problem at hand. For example, if you have to write
a program to generate and store a list of prime numbers between 1 and 10,00,000, and then display them in the same
order in which they were generated, you need to insert all the nodes at the end of the list.
However, if you have to display the prime numbers in the reverse order, you need to insert all the nodes at the beginning
of the list.

Again, consider that you are given a list of student records that needs to be stored in the ascending order of marks. In this
case, you may need to insert a new record at any position in the list, including the beginning of the list, end of the list, or
between any two nodes in the list.

Let us now write algorithms for inserting a node at the various positions in a linked list.

Inserting a Node at the Beginning of the List

The following algorithm depicts the logic to insert a node at the beginning of the linked list:

1. Allocate memory for the new node.

2. Assign a value to the data field of the new node.

3. Make the next field of the new node point to START (that is the first node in the list).

4. Make START point to the new node.

The process of inserting a node at the beginning of a list is illustrated in the following table.

Operation Illustration

Allocate memory and


assign the value to the
data field of the new
node.
Make the next field of
the new node point to
the first node in the list.

Make START point to


the new node.

The Insertion of a Node at the Beginning of a Singly-Linked List

Inserting a Node at the End of the List

The following algorithm depicts the logic to insert a node at the end of the linked list:

1. Allocate memory for the new node.

2. Assign a value to the data field of the new node.

3. If START is NULL, then:

a. Make START point to the new node.

b. Go to the step, 6.

4. Locate the last node in the list, and mark it as currentNode. To locate the last node in the list, execute the following
steps:

a. Mark the first node as currentNode.

b. Repeat the step, c, until the successor of currentNode becomes NULL.

c. Make currentNode point to the next node in the sequence.

5. Make the next field of currentNode point to the new node.

6. Make the next field of the new node point to NULL.

The process of inserting a node at the end of a list is illustrated in the following table.
Operation Illustration

Allocate memory and assign


the value to the data field of
the new node.

Locate the last node in the


list, and mark it as
currentNode.

Make the next field of


currentNode point to the new
node.

Make the next field of the new


node point to NULL.

The Insertion of a Node at the End of a Singly-Linked List

From the preceding algorithm, it is clear that inserting a node at the end of the linked list requires you to traverse to the
last node of the linked list. For instance, you have to solve a problem that always requires you to insert data at the end of
the linked list. In such a case, whenever you have to insert a node at the end of the linked list, you have to traverse to the
last node. If the list is long, this can be time consuming.

In such a case, it would be useful to have a variable/pointer, LAST, which always contains the address of the last node in
the list.

This is shown in the following figure.


The Singly-Linked List with LAST Variable/Pointer

The following algorithm is the modified algorithm that depicts the logic to insert a node at the end of the list:

1. Allocate memory for the new node.

2. Assign a value to the data field of the new node.

3. If START is NULL (if the list is empty), then:

a. Make START point to the new node.

b. Make LAST point to the new node.

c. Go to the step, 6.

4. Make the next field of LAST point to the new node.

5. Mark the new node as LAST.

6. Make the next field of the new node point to NULL.

Inserting a Node Between Two Nodes in the List

Consider that you have to store a set of student records in the increasing order of marks. In such a situation, you may need
to insert the new node at any position in the linked list. You have already seen how to insert a node at the beginning and at
the end of a linked list. Now, you will see how a node can be inserted between two nodes in an ordered linked list.

The following algorithm depicts the logic for inserting a node between two nodes in an ordered linked list:

1. Allocate memory for the new node.

2. Assign a value to the data field of the new node.

3. If START is NULL (if the list is empty), then:

a. Make the next field of the new node point to NULL.

b. Make START point to the new node.

c. Exit.

4. Identify the nodes between which the new node is to be inserted. Mark them as previous and current. To locate
previous and current, execute the following steps:

a. Make current point to the first node.

b. Make previous point to NULL.

c. Repeat the steps, d and e, until current.info becomes greater than newnode.info or current becomes equal to
NULL.

d. Make previous point to current.

e. Make current point to the next node in the sequence.


5. Make the next field of the new node point to current.

6. If previous is not NULL:

a. Make the next field of previous point to the new node.

b. Exit.

7. Make START point to the new node.

Consider the linked list, as shown in the following figure.

The Singly-Linked List Before Insertion of a Node

The process of inserting the node, 16, in the preceding list is illustrated in the following table.

Operation Illustration

Allocate memory and assign


the value to the data field of
the new node.

Identify the nodes between


which the new node is to be
inserted. Mark them as
previous and

current.
Make the next field of the
new node point to current.

Make the next field of


previous point to the new
node.

The Insertion of a Node Between Two Nodes in a Singly-Linked List

Deleting a Node from a Singly-Linked List

Deletion of a node refers to the process of deleting a node from the linked list. Before implementing a delete operation,
you need to check whether the list is empty or not. If the list is empty, an error message needs to be shown.

If the list is not empty, you need to first search the node to be deleted. If the specified node is not found in the list, an
error message needs to be shown.

You can delete a node from one of the following places in a linked list:

• Beginning of the list

• Between two nodes in the list

• End of the list

Deleting a Node from the Beginning of the List

The following algorithm depicts the logic to delete a node from the beginning of a list:

1. Mark the first node in the list as current.

2. Make START point to the next node in the sequence.


3. Release the memory for the node marked as current.

The process of deleting a node from the beginning of a list is illustrated in the following table.

Operation Illustration

Mark the first node in the list


as current.

Make START point to the next


node in the sequence.

Release the memory for

the node marked as

current.

The Deletion of a Node from the Beginning of a Singly-Linked List

Deleting a Node Between Two Nodes in the List

To delete a node between two nodes in the list, you first need to search the node to be deleted. The following algorithm
depicts the logic to delete a node between two nodes in the list:

1. Locate the node to be deleted. Mark the node to be deleted as current and its predecessor as previous. To locate
current and previous, execute the following steps:

a. Set previous = START.

b. Set current = START.

c. Repeat the steps, d and e, until the value of current matches the value to be deleted or current becomes NULL.

d. Make previous point to current.

e. Make current point to the next node in the sequence.

2. If current is NULL:
a. Display “Value not found in list.”.

b. Exit.

3. If current points to the first node of the list:

a. Make START point to the next node in the sequence.

b. Go to the step, 5.

4. Make the next field of previous point to the successor of current.

5. Release the memory for the node marked as current.

The process of deleting a node between two nodes in a list is illustrated in the following table.

Operation Illustration

Locate the node to be deleted.


Mark the node to be deleted
as current and its predecessor
as previous.

Make the next field of


previous point to the successor
of current.

Release the memory for the


node marked as current.

The Deletion of a Node Between Two Nodes in a Singly-Linked List

Activity 6: Implementing a Singly-Linked List


Problem Statement

Write a program to implement the insert, search, delete, and traverse operations on a singly-linked list, which stores the
records of the students in a class. Each record holds the following information:

Roll number of the student

Name of the student

Solution in Java

To implement insert, search, delete, and traverse operations on a singly-linked list that stores the records of students in a
class, you need to perform the following tasks:

1. Write the code in NetBeans.

2. Execute the code and verify the output.

Task 1: Writing the Code in NetBeans

To write the code in NetBeans, you need to perform the following steps:

1. Open NetBeans.

2. Create a new project with the name, Implementing_Singly_Linked_Lists.

3. Locate implementing_singly_linked_lists package in Projects window

4. Create Node.java file in it.

5. Replace the existing code in the Node.java file with the following code:

package implementing_singly_linked_lists;

public class Node {

public int rollNumber;

public String name;

public Node next;

6. Locate the Implementing_Singly_Linked_Lists.java file.

7. Rename this file to SinglyLinkedList.

8. Replace the code given in the SinglyLinkedList.java file with the following code:

package implementing_singly_linked_lists;

import java.util.*;

/* Create a class to represent the linked list. To represent a linked list,

you only need a reference to the first node of the list. */


public class SinglyLinkedList

public Node START; //Reference to the first node of the list.

public SinglyLinkedList ()

START = null;

public void addNode() // Adds a Node in the list

int rollNo;

String nm;

System.out.print("\nEnter the roll number of the student: ");

rollNo = Integer.parseInt(new Scanner(System.in).nextLine());

System.out.print("\nEnter the name of the student: ");

nm = new Scanner(System.in).nextLine();

// create a new node and assign values to it

Node newnode = new Node();

newnode.rollNumber = rollNo;

newnode.name = nm;

/* Locate the position of the new node in the list */

Node previous, current;

previous = null;

current = START;

/* Scan through the linked list until you find a node whose roll number is

greater than the roll number of the new node.*/

while ((current != null) && (rollNo >= current.rollNumber))

if (rollNo == current.rollNumber)

System.out.println("\nDuplicate roll numbers not allowed\n");


return;

previous = current;

current = current.next;

/* Once the above for loop is executed, prev and current are positioned in

such a manner that the position for the new node is in between them. */

newnode.next = current;

if(previous==null)

START=newnode;

else

previous.next = newnode;

}
public boolean delNode(int rollNo) /* Deletes the specified node from the

list */

Node previous, current;

previous=null;

current = START;

while ((current != null) && (rollNo != current.rollNumber))

previous = current;

current = current.next;

if(current == null)

return false;

else

{
/* If the node is present in the list, the following statements will be

executed */

if (current == START)

START = START.next;

else

previous.next = current.next;

return true;

public Node Search(int rollNo) // Checks whether the specified node is

present in the list or not

Node current = START;

/* Scan the linked list to search for the specified roll number */

while ((current != null) && (rollNo != current.rollNumber))

current = current.next;

return current;

public void traverse() // Displays the contents of the list

if (listEmpty())

System.out.println("\nList is empty.\n");

else

{
System.out.println("\nThe records in the list are :\n");

Node currentNode;
for (currentNode = START; currentNode != null; currentNode =

currentNode.next)

System.out.print(currentNode.rollNumber + " " +currentNode.name +

"\n");

System.out.println();

public boolean listEmpty() // Checks whether the list is empty

if (START == null)

return true;

else

return false;

public static void main(String[] args)

SinglyLinkedList obj = new SinglyLinkedList ();

while (true)

try

System.out.println("\nMenu");
System.out.println("1. Add a record to the list");

System.out.println("2. Delete a record from the list");

System.out.println("3. View all the records in the list");

System.out.println("4. Search for a record in the list");

System.out.println("5. Exit");

System.out.print("\nEnter your choice (1-5): ");

char ch = new Scanner(System.in).nextLine().charAt(0);

switch (ch)

case '1':

obj.addNode();

break;

case '2':

if (obj.listEmpty())

System.out.println("\nList is empty");

break;

System.out.print("\nEnter the roll number of the

student whose record is to be deleted: ");

int rollNo = Integer.parseInt(new

Scanner(System.in).nextLine());

System.out.println();
if (obj.delNode(rollNo) == false)

System.out.println("\nRecord not found.");

else

System.out.println("Record with roll number " +


rollNo + " deleted ");

break;

case '3':

obj.traverse();

break;

case '4':

if (obj.listEmpty() == true)

System.out.println("\nList is empty");

break;

System.out.print("\nEnter the roll number of the student whose record is

to be searched: ");

int num = Integer.parseInt(new Scanner(System.in).nextLine());

Node result=obj.Search(num);

if ( result== null)

System.out.println("\nRecord not found.");

else

System.out.println("\nRecord found");

System.out.println("\nRoll number: " + result.rollNumber);

System.out.println("\nName: " + result.name);

break;

case '5':

System.exit(0);

default:

System.out.println("\nInvalid option");

break;
}

catch (RuntimeException e)

System.out.println("\nCheck the value entered.");

9. Save the project.

Task 2: Executing the Code and Verifying the Output

Implementing a Doubly-Linked List

Consider that you need to implement a singly-linked list to store the marks of students in the ascending order. To display
the marks of students in the ascending order, you can simply traverse the list starting from the first node.

Now, consider another case where you need to display these marks in the descending order. This problem could be easily
solved if you could traverse the list in the reverse direction. However, as each node in a singly-linked list contains the
address of the next node in the sequence, traversal is possible in the forward direction only.

To solve this problem, each node in a linked list can be made to hold the reference of the preceding node, in addition, to its
next node in the sequence. Such a type of linked list is known as a doubly-linked list. In a doubly-linked list, each node
contains the address of its next node, as well as its previous node. This allows the flexibility to traverse in both the
directions.

The various operations in a doubly-linked list include insertion, deletion, search, and traversal.

Representing a Doubly-Linked List

A doubly-linked list in a program can be represented by declaring the following classes:

A class that represents a node in a doubly-linked list: In a doubly-linked list, each node needs to store:

• Information

• The address of the next node in the sequence

• The address of the previous node

In order to represent each node of a doubly-linked list, you can create the class named Node that contains data members
of varying data types to store information associated with the node. In addition, it contains the two variables/pointers,
next and prev, to hold the address of the next node and the previous node, respectively. The first node in the list contains
NULL in its prev variable/pointer and the address of the next node in its next variable/pointer. Similarly, the last node in
the list contains NULL in its next variable/pointer and the address of the previous node in its prev variable/pointer.
Refer to the following figure for the structure of a node in a doubly-linked list.

The Structure of a Node in a Doubly-Linked List

Traversing a Doubly-Linked List

A doubly-linked list enables you to traverse the list in the forward direction, as well as in the backward direction.

The following algorithm depicts the logic for traversing a doubly-linked list in the forward direction:

1. Mark the first node in the list as currentNode.

2. Repeat the steps, 3 and 4, until currentNode becomes NULL.

3. Display the information contained in the node marked as currentNode.

4. Make currentNode point to the next node in the sequence.

Please note that the algorithm for traversing a doubly-linked list in the forward direction is same as that for a singly-linked
list.

The following algorithm depicts the logic for traversing a doubly-linked list in the backward direction:

1. Mark the last node in the list as currentNode.

2. Repeat the steps, 3 and 4, until currentNode becomes NULL.

3. Display the information contained in the node marked as currentNode.

4. Make currentNode point to the node preceding it.

Inserting a Node in a Doubly-Linked List

Insertion involves adding a new node to an existing list or creating a new list if one does not exist. Therefore, to insert a
node in a doubly-linked list, you need to first check whether the list is empty or not.

If the list is empty, the following algorithm can be used to insert a node in the linked list:

1. Allocate memory for the new node.

2. Assign a value to the data field of the new node.

3. Make the next field of the new node point to NULL.


4. Make the prev field of the new node point to NULL.

5. Make START point to the new node.

Once the first node is inserted, the subsequent nodes can be inserted at any of the following positions:

• Beginning of the list

• Between two nodes in the list

• End of the list

Now, you will learn to write algorithms for inserting a node at the various positions in a linked list.

Inserting a Node at the Beginning of the List

The following algorithm depicts the logic for inserting a node at the beginning of a doubly-linked list:

1. Allocate memory for the new node.

2. Assign a value to the data field of the new node.

3. Make the next field of the new node point to the first node in the list.

4. Make the prev field of START point to the new node.

5. Make the prev field of the new node point to NULL.

6. Make START point to the new node.

Inserting a Node Between Two Nodes in the List

To insert a node between two nodes of an ordered doubly-linked list, you first need to search the nodes between which
the new node is to be inserted and mark them as previous and current. The following algorithm depicts the logic for
inserting a node between two nodes in a doubly-linked list:

1. Allocate memory for the new node.

2. Assign a value to the data field of the new node.

3. If START is NULL (if the list is empty), then:

a. Make the next field of the new node point to NULL.

b. Make the prev field of the new node point to NULL.

c. Make START point to the new node.

4. Identify the nodes between which the new node is to be inserted. Mark them as previous and current,
respectively. To locate previous and current, execute the following steps:

a. Make current point to the first node.

b. Make previous point to NULL.

c. Repeat the steps, d and e, until current.info > newnode.info or current = NULL.
d. Make previous point to current.

e. Make current point to the next node in the sequence.

5. Make the next field of the new node point to current.

6. Make the prev field of the new node point to previous.

7. Make the prev field of current point to the new node.

8. If previous is not NULL:

Make the next field of previous point to the new node.

9. If previous is NULL:

Make START point to the new node.

The process of inserting a node between two nodes in a doubly-linked list is illustrated in the following table.

Operation Illustration

Allocate memory for the new


node, and assign the value to
the data field of new node.

Identify the nodes between


which the new node is to be
inserted. Mark them as
previous and current.
Make the next field of new
node point to current.

Make the prev field of new


node point to previous.

Make the prev field of current


point to the new node.
Make the next field of
previous point to the new
node.

The Insertion of a Node Between Two Nodes in a Doubly-Linked List

If after the initial search operation, current is found to be NULL, it means that the node is to be inserted at the end of the
list. In this case, the step, 7, will give an error. This is because NULL cannot have the prev field. Therefore, the preceding
algorithm cannot be used to insert a node at the end of the list.

However, you can modify the preceding algorithm to solve this problem. Consider the following algorithm:

1. Allocate memory for the new node.

2. Assign a value to the data field of the new node.

3. If START is NULL (if the list is empty):

a. Make the next field of the new node point to NULL.

b. Make the prev field of the new node point to NULL.

c. Make START point to the new node.

4. Identify the nodes between which the new node is to be inserted. Mark them as previous and current,
respectively. To locate previous and current, execute the following steps:

a. Make current point to the first node.

b. Make previous point to NULL.

c. Repeat the steps, d and e, until current.info > newnode.info or current = NULL.

d. Make previous point to current.

e. Make current point to the next node in the sequence.

5. Make the next field of the new node point to current.

6. Make the prev field of the new node point to previous.

7. If current is not NULL:

Make the prev field of current point to the new node.

8. If previous is not NULL:


Make the next field of previous point to the new node.

9. If previous is NULL:

Make START point to the new node.

The preceding algorithm can now also be used to insert nodes at the end of the linked list. The process of inserting a node
at the end of the linked list is illustrated in the following table.

Operation Illustration

Allocate memory and assign the value to the


data field of the new node.

Make the next field of the new node point to


current that is NULL in this case.

Make the prev field of the new node point to


previous.
Make the next field of previous point to the new
node.

The Insertion of a Node at the End of a Doubly-Linked List

Inserting a Node at the End of the List

The algorithm for inserting a node between two nodes in a doubly-linked list can also be used to insert a node at the end
of the list. However, if you always have to insert a node at the end of the list only, it will be better to have the
variable/pointer, LAST, which contains the address of the last node in the list.

You can then use the following algorithm to insert a node at the end of the list:

1. Allocate memory for the new node.

2. Assign a value to the data field of the new node.

3. If LAST is not NULL:

Make the next field of the node marked as LAST point to the new node.

4. Make the prev field of the new node point to the node marked LAST.

5. Make the next field of the new node point to NULL.

6. Mark the new node as LAST.

Deleting Nodes from a Doubly-Linked List

The delete operation in a doubly-linked list is different from that of a singly-linked list. This is because in contrast to a
singly-linked list, each node in a doubly-linked list has an additional field pointing to its previous node. Therefore, you need
to adjust both the fields while performing the delete operation.

Before performing the delete operation, you first need to check whether the list is empty. If the list is empty, an error
message is shown.

However, if the list is not empty, you need to identify the position of the node to be deleted in the list. You can delete a
node from one of the following places in a doubly-linked list:

• Beginning of the list

• Between two nodes in the list

• End of the list

Deleting a Node from the Beginning of the List


You can use the following algorithm to delete a node from the beginning of a doubly-linked list:

1. Mark the first node in the list as current.

2. Make START point to the next node in the sequence.

3. If START is not NULL(if the deleted node was not the only node in the list), then:

Assign NULL to the prev field of the node marked as START.

4. Release the memory of the node marked as current.

The process of deleting a node from the beginning of the doubly-linked list is illustrated in the following table.

Operation Illustration

Mark the first node in the list as


current.

Make START point to the next


node in the sequence.

Assign NULL to the prev field of


the node marked as START.

Release the memory of the node


marked as current.

The Deletion of a Node from the Beginning of the Doubly-Linked List


Deleting a Node Between Two Nodes in the List

To delete a node between two nodes in the list, you first need to search the node to be deleted. Once found, mark the
node to be deleted as current, and its predecessor as previous. The following algorithm deletes a node from the middle of
the doubly-linked list:

1. Mark the node to be deleted as current and its predecessor as previous. To locate previous and current, execute
the following steps:

a. Make previous point to NULL. // Set previous = NULL

b. Make current point to the first node in the linked list (that is, Set current = START).

c. Repeat the steps, d and e, until either the value of current is same as the value to be deleted or current
becomes NULL.

d. Make previous point to current.

e. Make current point to the next node in the sequence.

2. If current is NULL:

a. Display “Value to be deleted not found in the list”.

b. Exit.

3. If previous is not NULL:

Make the next field of previous point to the successor of current.

4. If previous is NULL:

Make START point to its successor.

5. Make the prev field of the successor of current point to previous.

6. Release the memory of the node marked as current.

The process of deleting a node between two nodes in the doubly-linked list is illustrated in the following table.

Operation Illustration

Make the next field of previous


point to the successor of current.
Make the prev field of the
successor of current point to
previous.

Release the memory of the node


marked as current.

The Deletion of a Node Between Two Nodes in the Doubly-Linked List

If after the initial search operation, the next field of current is found to have the NULL value, it means that the node is to
be deleted from the end of the list. In this case, the step, 3, will give an error. This is because the successor of current is
NULL and therefore, it cannot have the prev field. Therefore, the preceding algorithm cannot be used to insert a node at
the end of the list.

However, you can modify the preceding algorithm to solve this problem. Consider the following algorithm:

1. Mark the node to be deleted as current and its predecessor as previous.

2. If current is NULL:

a. Display “Value to be deleted not found in the list”.

b. Exit.

3. If previous is not NULL:

Make the next field of previous point to the successor of current.

4. If previous is NULL:

Make START point to its successor.

5. If the successor of current exists:

Make the prev field of the successor of current point to previous.

6. Release the memory of the node marked as current.

Solving Programming Problems Using Stacks and Queues

At times, while solving programming problems, you may need to store or retrieve a list of items. Consider a situation
where you need to maintain a list of student records in the ascending order of their names. To maintain such a list, you
may need to insert or delete records from any position in the list. There can be a situation where you want to implement
this list in such a way that allows items to be inserted and deleted at only one end of the list. This kind of a list can be
implemented by using a stack.

Moreover, there are programming problems that require you to implement a list in such a way that items can be retrieved
in the same order in which they are inserted in the list. Such a list can be implemented by using a data structure called
queue.

Solving Programming Problems by Using Stacks

Consider an example of a card game called Rummy. To start the game, a stock pile and a discard pile are placed in the
center. A player can draw either the topmost card of the stock pile or the topmost card of the discard pile. If the drawn
card does not make a valid sequence in the player’s hand, the player can discard the card by placing it on the top of the
discard pile.

The next player can then draw either the topmost card of the stock pile or the topmost card of the discard pile, and so on.

To represent and manipulate such type of a discard pile in a computer program, you need a data structure that allows
insertion and deletion at only one end. It should ensure that the last item inserted is the first one to be removed. A data
structure that implements this concept is called a stack.

This section discusses the stack data structure and explains the operations that can be performed on a stack.

Defining a Stack

A stack is a collection of data items that can be accessed at only one end, which is called top. This means that the items are
inserted and deleted at the top. The last item that is inserted in a stack is the first one to be deleted. Therefore, a stack is
called a Last-In-First-Out (LIFO) data structure.

A stack is like an empty box containing books, which is just wide enough to hold the books in one pile. The books can be
placed, as well as removed, only from the top of the box. The book most recently put in the box is the first one to be taken
out. The book at the bottom is the first one to be put inside the box and the last one to be taken out.

The following figure shows a stack of books.

A Stack of Books

The characteristics of stacks are:


• Data can only be inserted on the top of the stack.

• Data can only be deleted from the top of the stack.

• Data cannot be deleted from the middle of the stack. All the items from the top first need to be removed.

Identifying the Operations on Stacks

The following two basic operations can be performed on a stack:

• PUSH

• POP

When you insert an item into a stack, you say that you have pushed the item into the stack. When you delete an item from
a stack, you say that you have popped the item from the stack.

The following table depicts the PUSH and POP operations on a stack.

Operation Stack Contents

Push book 1 into an empty stack

Push book 2 into the stack

Pop a book from the stack


Push book 3 into the stack

Push book 4 into the stack

Pop book 3 from the stack Invalid Operation. To pop book 3, you need to
first pop book 4 from the stack.

Pop a book from the stack

The PUSH and POP Operations on a Stack

Implementing a Stack

A stack is simply a list in which insertion and deletion is allowed only at one end, which is known as the top of the stack. A
stack can be implemented by using an array or a linked list.

When a stack is implemented by using a linked list, the stack is said to be dynamic. In this case, memory is dynamically
allocated to the stack, and the size of the stack can grow and shrink at runtime.

To represent a stack as a linked list, you need to first declare a class to represent a node in the linked list. The following
code snippets give the Java declarations of a class named Node that represents a node in a linked list:

// Code in Java

class Node

public int info;

public Node next;

public Node(int i, Node n)

{
info = i;

next = n;

After representing a node of a stack, you need to declare a class to implement operations on a stack. In this class, you also
need to declare a variable/pointer to hold the address of the topmost element in the stack and initialize this
variable/pointer to contain the value, NULL.

The following code snippets give the Java declarations of a class named Stack that implements the operations on a stack:

// Code in Java

class Stack

Node top;

public Stack()

top = null;

public boolean empty()

// Returns true if the stack is empty, false otherwise

// Statements

public void push(int element)

// Statements

public void pop()

// Statements
}

After declaring the class to implement the operations on a stack, you need to implement the PUSH and POP operations.

Implementing the PUSH Operation

To implement the PUSH operation, you need to insert a node at the beginning of the linked list. The following algorithm
depicts the logic for the PUSH operation:

1. Allocate memory for the new node.

2. Assign value to the data field of the new node.

3. Make the next field of the new node point to top.

4. Make top point to the new node.

Implementing the POP Operation

To implement the POP operation, you need to delete a node from the beginning of the linked list. The following algorithm
depicts the logic for the POP operation:

1. Make a variable/pointer tmp point to the topmost node.

2. Retrieve the value contained in the topmost node.

3. Make top point to the next node in sequence.

4. Release memory allocated to the node marked by tmp.

When deleting a node, you need to check whether the stack contains any element. If you attempt to pop an element from
an empty stack, there is an underflow. Therefore, before popping an element from the stack, you need to check the stack
empty condition. The condition for stack empty is:

top = NULL

If the stack empty condition is true, the POP operation should not be performed.

The following modified algorithm depicts the logic for the POP operation:

1. If top = NULL:

a. Display “Stack Underflow: Cannot delete from an empty stack”.

b. Exit.

2. Make tmp point to the topmost node.

3. Retrieve the value contained in the topmost node.

4. Make top point to the next node in sequence.


5. Release the memory allocated to the node marked as tmp.

Activity 7 Solving Programming Problems by Using Stacks

Problem Statement

Write a program to implement a stack by using a linked list.

Solution in Java

To implement a stack by using a linked list, you need to perform the following tasks:

1. Write the code in NetBeans.

2. Execute the code and verify the output.

Task 1: Writing the Code in NetBeans

To write the code in NetBeans, you need to perform the following steps:

1. Open NetBeans.

2. Create a new project with the name, Stack_Implementation.

3. Locate stack_implementation package in the Projects window.

4. Add the Node.java file.

5. Replace all the existing code in the Node.java file with the following code:

package stack_implementation;

/* Class to represent a node of the stack */

public class Node

public int info; // Variable to store data

public Node next; // Variable to store address of next node in the

stack

public Node(int i, Node n) // Constructor to initialize node

contents

info = i;

next = n;

}
6. Save the Node.java file.

7. Locate Stack_Implementation.java file.

8. Rename it to Stack.

9. Replace the code given in the Stack.java file with the following code:

package stack_implementation;

import java.util.*;

/* Class to represent a stack */

public class Stack

private Node top; // Refers to the top of the stack

public Stack() // Initializes stack

top = null;

private boolean empty() // Checks whether the stack is empty

if (top == null)

return (true);

else

return (false);

public void push(int element) // Inserting an element in the stack

Node fresh; // refers to new node

fresh = new Node(element, null); // allocating memory to the new

node
fresh.next = top; /* Make next field of the new node point to the

top of the stack */

top = fresh; // Make top point to the new node

System.out.println("\n" + element + " pushed.");

public void pop() // Pops an element from the stack

System.out.println("\nThe popped element is: " + top.info);

top = top.next; /* Make top point to the next node in

sequence */

public void display()

Node tmp;

if (empty()) // If stack is empty

System.out.println("\nStack Empty");

else

System.out.println("\nStack Elements:");

// Traverse the list from beginning till end

for (tmp = top; tmp != null; tmp = tmp.next)

System.out.println(tmp.info);

System.out.println();

public static void main(String[] args)


{

Stack s = new Stack();

while (true)

System.out.println();

System.out.println("\n***Stack Menu***\n");

System.out.println("1. Push.");

System.out.println("2. Pop");

System.out.println("3. Display");

System.out.println("4. Exit");

System.out.print("\nEnter your choice: ");

char ch = new Scanner(System.in).nextLine().charAt(0);

switch (ch)

case '1':

System.out.print("\nEnter a number: ");

int num = Integer.parseInt(new

Scanner(System.in).nextLine());

s.push(num);

break;

case '2':

if (s.empty())

System.out.println("\nStack Empty");

break;

s.pop();

break;

case '3':

s.display();
break;

case '4':

return;

default:

System.out.println("\nInvalid Choice");

break;

10. Save the project.

Task 2: Executing the Code and Verify the Output

Solving Programming Problems by Using Queues

Consider the scenario of a bank where there is only one counter to resolve customer queries. Therefore, customers have
to stand in a queue to get their queries resolved. To avoid the inconvenience caused to the customers, the bank has
decided to set up a new system.

According to this system, whenever a customer visits the bank, a request entry will be made into the system and the
customer will be given a request number. The requests will be stored in the system in the order in which they are received.
The request number of the earliest request will be automatically flashed on the query counter to indicate that the
customer with that request number can come next to get his/her query resolved. When a customer’s request has been
processed, the request will be removed from the system.

To implement such a system, you need a data structure that stores and retrieves the requests in the order of their arrival. A
data structure that implements this concept is a queue.

Defining Queues

A queue is a list of elements in which items are inserted at one end of the queue and deleted from the other end of the
queue. You can think of a queue as an open ended pipe, with elements being pushed from one end and coming out of
another. The end at which elements are inserted is called the rear, and the end from which the elements are deleted is
called the front. A queue is also called a First-In-First-Out (FIFO) list because the first element to be inserted in the queue is
the first one to be deleted. The following figure represents a queue.
The Structure of a Queue

The queue data structure is similar to the queues in real life. Consider the scenario of a cafeteria where there is a queue of
customers at the counter. The customer standing first in the queue is served first. When new customers arrive, they are
made to stand at the rear end of the queue. After being served, a customer moves away from the queue and the next
person is there to be served.

Identifying the Various Operations on Queues

The following two types of operations can be performed on queues:

Insert: It refers to the addition of an item in the queue. Items are always inserted at the rear end of the queue. Consider
the queue, as shown in the following figure.

The Queue Before Insertion

In the preceding queue, there are five elements. The element, B, is labeled as FRONT to indicate that it is the first element
in the queue. Similarly, the element, D, is labeled as REAR to indicate that it is the last element in the queue.

Now, suppose you want to add an item, F, in the queue. Since addition takes place at the rear end of the queue, F will be
inserted after D. Now, F becomes the rear end of the queue. Hence, we label F as REAR, as shown in the following figure.

The Queue After Insertion


Delete: It refers to the deletion of an item from a queue. Items are always deleted from the front of the queue. When an
item is deleted, the next item in the sequence becomes the front end of the queue. Consider the queue, as shown in the
following figure.

The Queue Before Deletion

On implementing a delete operation, the item, B, will be removed from the queue. Now, the item, A, will become the new
front end of the queue. Therefore, the item, A, is labeled as FRONT, as shown in the following figure.

The Queue After Deletion

Implementing a Queue

You can implement a queue by using an array or a linked list. A queue implemented in the form of a linked list is known as
a linked queue.

To represent a queue in the form of a linked list, you need to declare two classes:

A class to represent a node in the queue: This class represents a node in the linked queue, and is similar to the node of a
singly-linked list. Refer to the following Java declarations of a class named Node that represents a node in a queue:

// Code in Java

class Node

public int data;

public Node next;

}
A class that represents the queue: This class implements all the operations on a queue, such as insert and remove. In
addition, it declares two variables/pointers, FRONT and REAR, which point to the first and last elements in the queue,
respectively. Initially, FRONT and REAR are made to point to NULL indicating that the queue is empty. Refer to the following
declarations of the LinkedQueue class in Java:

// Code in Java

class LinkedQueue

Node FRONT, REAR;

public LinkedQueue()

FRONT = null;

REAR = null;

public void insert (int element){}

public void remove(){}

public void display(){}

The structure of a linked queue is shown in the following figure.

The Linked Queue

After deciding the representation for the linked queue, you can start writing algorithms for inserting and deleting elements
in the queue.

Inserting an Element in a Linked Queue

An element is always inserted at the rear end of the queue. Therefore, you need to insert a new node at the rear end of
the linked queue. The following algorithm depicts the logic for inserting a node at the rear end of a linked queue:

1. Allocate memory for the new node.

2. Assign value to the data field of the new node.

3. Make the next field of the new node point to NULL.


4. If the queue is empty, execute the following steps:

a. Make FRONT point to the new node.

b. Make REAR point to the new node.

c. Exit.

5. Make the next field of REAR point to the new node.

6. Make REAR point to the new node.

The process of inserting an element in a linked queue is shown in the following table.

Operation Illustration

Allocate memory and assign value to


the data field for the new node.

Make the next field of the new node


point to NULL.

Make the next field of REAR point to the


new node.

Make REAR point to the new node.

The Insert Operation in a Linked Queue

Deleting an Element from a Linked Queue

An element is always deleted from the front end of a queue. Therefore, you need to delete a node from the front end of
the linked queue. The following algorithm depicts the logic for deleting a node from the front end of a linked queue:

1. If the queue is empty: // FRONT = NULL

a. Display “Queue empty”.

b. Exit.
2. Mark the node marked FRONT as current.

3. Make FRONT point to the next node in its sequence.

4. Release memory for the node marked as current.

The process of deleting an element from a linked queue is shown in the following table.

Operation Illustration

Mark the node marked FRONT as


current.

Make FRONT point to the next node in


its sequence.

Release memory for the node marked as


current.

The Delete Operation in a Linked Queue

Activity 8: Solving Programming Problems by Using Queues

Problem Statement

Write a program to implement insert and delete operations on a linked queue.

Solution in Java

To implement insert and delete operations on a linked queue, you need to perform the following tasks:

1. Write the code in NetBeans.

2. Execute the code and verify the output.

Task 1: Writing the Code in NetBeans

To write the code in NetBeans, you need to perform the following steps:

1. Open NetBeans.
2. Create a new project with the name, Implement_Queue.

3. Locate the implement_queue package in the Projects window.

4. Add the Node.java file.

5. Replace all the existing code in the Node.java file with the following code:

package implement_queue;

public class Node

public int data;

public Node next;

public Node(int d, Node n) // Creates node

data = d;

n = next;

6. Save the Node.java file.

7. Rename Implement_Queue.java to Queue.java.

8. Replace the code given in the Queue.java file with the following code:

package implement_queue;

import java.util.*;

/* Class to represent a queue */

public class Queue {

private Node FRONT, REAR; /* Pointers to the first and last nodes of the

queue */

public Queue()

/* Set FRONT and REAR to null as the queue is initially empty. */

FRONT = null;

REAR = null;

}
public void insert(int element) // Inserts a node in the queue

Node newnode;

newnode = new Node(element, null);

if (FRONT == null)

/* If the queue is empty, then both FRONT and REAR should point to

the new node. */

FRONT = newnode;

REAR = newnode;

else

/* If the queue is NOT empty, then REAR points to the last node.

REAR should now point to the new node. */

REAR.next = newnode;

REAR = newnode;

System.out.println("\nNumber " + element + " is inserted into the

queue\n");

public void remove() // Deletes node from the queue

if (FRONT == null) // Check if the queue is empty

System.out.println("Queue is empty");

return;

else

{
System.out.println("\nThe element deleted from the queue is: " +

FRONT.data);

FRONT = FRONT.next; // FRONT will now point to the next node in the queue.

public void display() // displays the elements of the queue

Node tmp;

if (FRONT == null) // Checks if the queue is empty

System.out.println("Queue is empty");

return;

else

System.out.println("\nElements in the queue are\n");

for (tmp = FRONT; tmp != null; tmp = tmp.next) /* traverse the queue

through tmp node */

System.out.print(tmp.data + " ");

System.out.println();

public static void main(String[] args) {

Queue q=new Queue();

while (true)

try

{
System.out.println("\nMenu");

System.out.println("1. Implement insert operation in the queue");

System.out.println("2. Implement a delete operation on the queue");

System.out.println("3. Display values");

System.out.println("4. Exit");

System.out.print("\nEnter your choice (1-4): ");

char ch = new Scanner(System.in).nextLine().charAt(0);

System.out.println();

switch (ch)

case '1':

System.out.print("Enter a number: ");

int num = Integer.parseInt(new Scanner(System.in).nextLine());

q.insert(num);

break;

case '2':

q.remove();

break;

case '3':

q.display();

break;

case '4':

return;

default:

System.out.println("Invalid option!!");

break;

catch (RuntimeException e)

{
System.out.println("Check the values entered." + e.toString());

9. Save the project.

Task 2: Executing the Code and Verifying the Output

Solving Programming Problems Using Trees

Many programming problems require data to be stored in a hierarchical fashion. This can be done by using a data structure
called tree. A tree not only helps you represent the hierarchical relationship among data, but also provides an efficient
mechanism for data storage and retrieval.

Storing Data in a Tree

Consider a scenario where you need to represent the directory structure of your operating system. The directory structure
contains various folders and files. A folder may further contain any number of sub folders and files. Such an arrangement
enables you to keep the related files and folders together. The directory structure of an operating system is shown in the
following figure.

The Directory Structure


Suppose you want to represent a similar directory structure in the memory. It is difficult to represent the structure linearly
because all the items have a hierarchical relationship among themselves. To represent such a structure, it is better to have
a data storage mechanism that enables you to store data in a nonlinear fashion.

You can implement a tree to solve such a problem. Trees offer a lot of practical applications in the field of computer
science. For example, most of the modern operating systems have their file systems organized as trees.

Defining Trees

A tree is a nonlinear data structure that represents the hierarchical relationship among the various data elements, as
shown in the following figure.

A Sample Tree Structure

Each data element in a tree is called a node. The topmost node of a tree is called a root. The nodes in a tree are connected
to other nodes through edges. The only way to get from one node to the other is to follow the path along the edges.

Each node in a tree can have zero or more child nodes. However, each node in a tree has exactly one parent. An exception
to this rule is the root node, which has no parent. A node in a tree that does not have any child node is called a leaf node.

In the tree structure that represents a hierarchical file system, the root directory is the root of the tree. The directories that
are one level below the root directory are the child nodes. Similarly, we can have various levels of sub directories, thereby
forming a hierarchical structure. Files, on the other hand, will have no children. Therefore, they can be termed as the leaf
nodes.

Tree Terminology

There are various terms that are frequently used while working with trees. Let us explain these terms by using a tree, as
shown in the following figure.
A Tree

The following terms are associated with a tree:

Leaf node: A node with no children is called a leaf node. A leaf node is also known as a terminal node. In the preceding
figure, E, F, G, H, I, J, L, and M are leaf nodes.

Subtree: A portion of a tree, which can be viewed as a tree in itself, is called a sub tree. In the preceding figure, the tree
starting at node B, containing nodes E, F, G, and H, is a sub tree of the complete tree. A sub tree can also contain only one
node, which is called the leaf node. In other words, all the leaf nodes are sub trees, but all the sub trees are not leaf nodes.

Children of a node: The roots of the sub trees of a node are called the children of the node. In the

preceding figure, E, F, G, and H are the children of node B, and B is the parent of nodes: E, F, G, and H. Similarly, J and K are
the children of node D, and D is the parent of J and K.

Degree of a node: The number of sub trees of a node is called the degree of a node. In the preceding figure,

• Degree of node A is 3.

• Degree of node B is 4.

• Degree of node C is 1.

• Degree of node D is 2.

Edge: A link from the parent to the child node is known as an edge. It is also known as a branch. A tree with n nodes has n
– 1 edges.

Siblings/Brothers: Children of the same node are called siblings of each other. In the preceding figure,

• Nodes B, C, and D, are siblings of each other.

• Nodes E, F, G, and H, are siblings of each other.

• Nodes L and M, are siblings of each other.

Internal node: An intermediate node between the root node and the leaf node is called an internal node. It is also known
as a nonterminal node. In the preceding figure, nodes B, C, D, and K, are internal nodes.

Level of a node: The distance (in number of nodes) of a node from the root is called the level of a node. The root always
lies at level, 0. As you move down the tree, the level of a node increases in such a way that if a node is at level n, its
children are at level, n + 1. In the preceding tree, the level of node A is 0; level of nodes B, C, and D, is 1; level of nodes E, F,
G, H, I, J, and K, is 2; and level of nodes L and M, is 3.
Depth of a tree: The maximum number of levels in a tree is called the depth of a tree. In other words, the depth of a tree
is one more than the maximum level of the tree. The depth of a tree is also known as the height of a tree. In the preceding
figure, the depth of the tree is 4.

Implementing a Binary Tree

A binary tree is a special type of tree that offers a lot of practical applications in the field of computer science. This section
discusses the concept and implementation of a binary tree.

Defining Binary Trees

A binary tree is a specific type of tree in which each node can have a maximum of two children. These child nodes are
typically distinguished as the left and the right child. The structure of a binary tree is shown in the following figure.

The Structure of a Binary Tree

In the preceding binary tree, node B is the left child of node A, and node C is the right child of node A. Similarly, node D is
the left child of node B and node E is the right child of node B.

There are some variants of binary trees with some additional characteristics. These variants include:

Strictly binary tree: A binary tree is said to be strictly binary if every node, except the leaf nodes, has non empty left and
right children. An example of a strictly binary tree is shown in the following figure.

A Strictly Binary Tree

In the preceding binary tree, every non-leaf node has precisely two children. Therefore, it is a strictly binary tree.
Full binary tree: A binary tree of depth d is said to be a full binary tree if it has exactly 2d - 1 nodes. An example of a full
binary tree is shown in the following figure.

A Full Binary Tree

Complete binary tree: A binary tree in which all levels, except possibly the deepest level, are completely filled, and at the
deepest level, all nodes are as far left as possible.

An example of a complete binary tree is shown in the following figure.

A Complete Binary Tree

Representing a Binary Tree

Binary trees can be implemented by using an array or linked lists.

In a linked representation of a binary tree, you declare two classes:

A class to represent a node of the tree: This class represents the structure of each node in a binary tree. A node in a
binary tree consists of the following parts:

Information: It refers to the information held by each node in a binary tree.

Left child: It holds the reference of the left child of the node.

Right child: It holds the reference of the right child of the node.

Consider the structure of a node in a linked binary tree, as shown in the following figure.
The Structure of a Node in a Linked Binary Tree

Refer to the following Java declarations of a class named Node that represents a node of a tree:

// Code in Java

class Node

public int info;

public Node lchild;

public Node rchild;

A class to represent the binary tree: This class implements various operations on a binary tree, such as insert, delete, and
traverse. The class also provides the declaration of the variable/pointer root, which contains the address of the root node
of the tree. Refer to the following Java declarations of a class named BinaryTree that represents a binary tree in a program:

// Code in Java

class BinaryTree

public Node ROOT;

public BinaryTree()

ROOT = null;

public void insert(int element);

public void find(int element);

public void inorder(Node ptr);

public void preorder(Node ptr);

public void postorder(Node ptr);

};

Consider the linked representation of a binary tree, as shown in the following figure.
The Linked Representation of a Binary Tree

Traversing a Binary Tree

Traversal of nodes is one of the most common operations in a binary tree. Traversing a tree refers to the process of visiting
all the nodes of a tree once.

There are three ways in which you can traverse a binary tree. These are inorder traversal, preorder traversal, and postorder
traversal. The sequence of traversal in the three traversal techniques is different.

Consider the binary tree, as shown in the following figure.


The Binary Tree

Now, let us discuss the inorder, preorder, and postorder traversals by referring to the preceding binary tree.

Inorder Traversal

The following steps can be used for traversing a binary tree in the inorder sequence:

1. Traverse the left sub tree.

2. Visit the root.

3. Traverse the right sub tree.

For performing the inorder traversal of the given binary tree, you need to start at the root, and first traverse each node's
left branch, then the node, and finally the node's right branch. This is a recursive process because each node’s left and
right branch is a tree in itself.

The root of the tree is A. Before visiting A, you must traverse the left sub tree of A. Therefore, you move to node B. Now,
before visiting B, you must traverse the left sub tree of B. Therefore, you move to node D. Now, before visiting D, you must
traverse the left sub tree of node D. However, the left sub tree of node D is empty. Therefore, visit node D.

After visiting node D, you must traverse the right sub tree of node D. Therefore, you move to node H. Before visiting H, you
must traverse the left sub tree of H. However, H does not have a left sub tree. Therefore, visit node H.

After visiting node H, you must traverse the right sub tree of node H. The right sub tree of H is empty. Now the traversal of
the left sub tree of node B is complete. Therefore, visit node B.

After visiting node B, you must traverse the right sub tree of node B. Therefore, move to node E. Because E does not have a
left sub tree, visit E. Node E does not have a right sub tree either. Now, the traversal of the left sub tree of A is complete.
Therefore, visit node A.

After visiting node A, you must traverse the right sub tree of node A. Therefore, move to node C. Before visiting C, you
must visit the left sub tree of C. Therefore, move to node F. F does not have a left sub tree, so visit F. F does not have a
right sub tree either. At this stage, the traversal of the left sub tree of C is complete. Therefore, visit node C.

After visiting node C, you must traverse the right sub tree of C. Therefore, move to node G. Before, visiting node G, you
must traverse the left sub tree of node G. Therefore, move to node I. I does not have a left sub tree. Therefore, visit node I.

After visiting node I, you must traverse the right sub tree of node I. However, node I does not have a right sub tree. The
traversal of the left sub tree of node G is now complete. Therefore, visit node G. G does not have a right sub tree. At this
stage, the traversal of the right sub tree of node C is complete. Also, the traversal of the right sub tree of node A is
complete.

Therefore, the following sequence specifies the inorder traversal: DHBEAFCIG

The following figure depicts the sequence of traversal in the inorder traversal.
The Inorder Traversal

The following algorithm depicts the logic of inorder traversal:

Algorithm: Inorder (root)

1 If (root = NULL):

Exit.

2. Inorder (left child of root) // Recursive call to Inorder for traversing

// the left sub tree

3. Visit (root)

4. Inorder (right child of root) // Recursive call to Inorder for traversing the right sub tree

Preorder Traversal

The following steps can be used for traversing a binary tree in the preorder sequence:

1. Visit the root.

2. Traverse the left sub tree.

3. Traverse the right sub tree.

For a preorder traversal, you must begin with the root node. The root of the tree is A. Therefore, visit node A. Now, move
to the left sub tree of A. The root of the left sub tree of A is B. Hence, visit node B. Move to the left sub tree of node B, and
visit its root, D. Now, node D does not have a left sub tree. Therefore, move to its right sub tree. The root of its right sub
tree is node H. Therefore, visit node H. Now move to the right sub tree of node B and visit node E. This finishes the
traversal of the root and its left sub tree. Now, traverse the right sub tree of the root in a similar fashion.

Therefore, the following sequence specifies the preorder traversal: ABDHECFGI

The following figure depicts the sequence of traversal in the preorder traversal.
The Preorder Traversal

The following algorithm depicts the logic of preorder traversal:

Algorithm: Preorder (root)

1. If (root = NULL): Exit.

2. Visit (root)

3. Preorder (left child of root) // Recursive call to Preorder for traversing the left sub tree

4. Preorder (right child of root) // Recursive call to Preorder for traversing the right sub tree

Postorder Traversal

The following steps can be used for traversing a binary tree in the postorder sequence:

1. Traverse the left sub tree.

2. Traverse the right sub tree.

3. Visit the root.

For postorder traversal, the left and right sub trees of a node are traversed first, and then the node itself.

Therefore, begin by traversing the left sub tree of the root node A. The root of its left sub tree is B.

Therefore, move to node B.

From node B, you need to move further to its left sub tree. The root of its left sub tree is node D. Node D does not have a
left sub tree but has a right sub tree. Therefore, move to its right sub tree. The root of its right sub tree, which is node H,
does not have left and right sub trees. Therefore, node H will be the first node to be visited.

After having visited node H, the traversal of the right sub tree of node D is complete. Therefore, you need to visit node D.
Until now, the left sub tree of node B has been traversed. Now, you need to traverse the right sub tree of B before you visit
node B. The root of its right sub tree is node E, which does not have left and right sub trees. Hence, visit node E and then
visit node B. The left sub tree of node A has been traversed. You now need to traverse its right sub tree in a similar fashion
before you visit node A.

Therefore, the following sequence specifies the postorder traversal: HDEBFIGCA

The following figure depicts the sequence of traversal in the postorder traversal.
The Postorder Traversal

Implementing a Binary Search Tree

Consider a scenario of a cellular phone company that maintains the information of millions of its customers spread across
the world. Each customer is assigned a unique identification number (ID). Individual customer records can be accessed by
referring to the respective ID. These IDs need to be stored in a sorted manner in such a way that you can easily perform
various transactions, such as search, insertion, and deletion.

Which data structure will you use to solve this problem? If you use an array, you can perform a faster search. However,
insertion or deletion will be difficult in that case. If you use a linked list, insertion and deletion will be fast. However, the
search operation will be time consuming, especially if the data to be searched is at the end of the list.

In such a case, it is better to have a data storage mechanism, which provides the advantages of both, arrays as well as
linked lists. A special type of binary tree, which is known as a binary search tree, can be used in this case. A binary search
tree matches the search speed of arrays, and also offers efficient insertions and deletions as in the case of a linked list.

Defining a Binary Search Tree

A binary search tree is a binary tree in which the value of the left child of a node is always less than or equal to the value of
the node, and the value of the right child of a node is always greater than the value of the node. Consider the binary
search tree, as shown in the following figure.
The Binary Search Tree

The root node contains value, 52. All the nodes in the left sub tree of the root have a value less than 52. Similarly, all the
nodes in the right sub tree of the root node have values greater than 52. The same holds true for other sub trees as well.

Searching a Node in a Binary Search Tree

The search operation in a binary search tree refers to the process of searching a specific value in the tree. The following
algorithm depicts the logic of searching a particular value in a binary search tree:

1. Make currentNode point to the root node.

2. If currentNode is NULL:

a. Display “Not found”.

b. Exit.

3. Compare the value to be searched with the value of currentNode. Depending on the result of the comparison,
there can be three possibilities:

a. If the value is equal to the value of currentNode:

i. Display “Found”.

ii. Exit.

b. If the value is less than the value of currentNode:

i. Make currentNode point to its left child.

ii. Go to step 2.

c. If the value is greater than the value of currentNode:

i. Make currentNode point to its right child.

ii. Go to step 2.

In the preceding figure, suppose you want to search the value, 59. You begin the search from the root node, 52. Compare
52 with 59. Since 59 > 52, move to the right child of 52, that is 68. Compare 59 with 68. Since 59< 68, move to the left child
of 68, that is 59. Again, compare 59 with 59. The values are equal. This means that you have found the node in the binary
search tree.

From the preceding example, you can see that after every comparison, the number of elements to be searched reduces to
half. As a result, binary search trees provide quick access to data.

Inserting Nodes in a Binary Search Tree

The insert operation involves insertion of a new node into a binary search tree. The new node must be inserted in such a
way that the keys remain properly ordered. Before implementing an insert operation, you first need to check whether the
tree is empty or not. If the tree is empty, the new node to be inserted becomes the root of the tree. However, if the tree is
not empty, then you need to locate the position of the new node to be inserted in the binary search tree. A new node is
always inserted as a leaf node.
Consider a binary search tree, which is initially empty. Suppose you want to insert nodes in a binary search tree in the
following order:

52 36 68 24 44 72

The process of inserting a new node in a binary search tree is shown in the following table.

Operation Decisions Taken Illustration

Insert 52 The tree is empty; therefore,

node 52 becomes the root.

Insert 36 Compare the value 36 with

that of the root. Since 36 <

52, insert 36 as the left child

of 52.

Insert 68 Compare the value 68 with

that of the root. Since 68 >

52, insert 68 as the right

child of 52.

Insert 24 Compare the value 24 with

that of the root. Since 24 <

52, 24 will be inserted in the

left sub tree of the root.

However, the left sub tree of

the root is not empty.

Therefore, move to the left

child of 52, which is 36.

Compare the value 24 with

36. Since 24 < 36, insert 24

as the left child of 36.


Insert 44 Compare the value 44 with

that of the root. Since 44 <

52, 44 will be inserted in the

left sub tree of the root.

However, the left sub tree of

the root is not empty.

Therefore, move to the left

child of root, which is node

36. Compare the value 44

with 36. Since 44 > 36, insert

44 as the right child of 36.

The Process of Inserting Nodes in a Binary Search Tree

The following algorithm depicts the logic of inserting a node in a binary search tree:

1. Allocate memory for the new node.

2. Assign value to the data field of new node.

3. Make the left and right child of the new node point to NULL.

4. Locate the node that will be the parent of the node to be inserted. Mark it as parent. Execute the following steps
to locate the parent of the new node to be inserted:

a. Mark the root node as currentNode.

b. Make parent point to NULL.

c. Repeat steps d, e, and f, until currentNode becomes NULL.

d. Make parent point to currentNode.

e. If the value of the new node is less than or equal to that of currentNode: Make currentNode point to its left
child.

f. If the value of the new node is greater than that of currentNode: Make currentNode point to its right child.

5. If parent is NULL (Tree is empty):

a. Mark the new node as ROOT.

b. Exit.

6. If the value in the data field of the new node is less than or equal to that of parent:

a. Make the left child of parent point to the new node.


b. Exit.

7. If the value in the data field of the new node is greater than that of the parent:

a. Make the right child of parent point to the new node.

b. Exit.

Consider a binary search tree, as shown in the following figure.

The Binary Search Tree Before Insert Operation

Suppose you want to insert a node 55 in the given binary search tree. The process of inserting is shown in the following
table.

Operation Illustration

Allocate memory and assign a value to the data


field of the new node. Make the left and right
child fields of the new node point to NULL.
Locate the node that will be the parent of the
node to be inserted. Mark it as parent.

If the value in the data field of the new node is


less than or equal to the value of parent, make
the left child of parent point to the new node.

The Process of Inserting a Node in a Binary Search Tree

Deleting Nodes from a Binary Search Tree

The delete operation in a binary search tree refers to the process of deleting a specified node from the tree.

To delete a node from a binary search tree, you first need to locate the node to be deleted and its parent node.

The following algorithm depicts the logic to locate the node to be deleted and its parent:

1. Make a variable/pointer currentNode point to the ROOT node.

2. Make a variable/pointer parent point to NULL.

3. Repeat steps a, b, and c until currentNode becomes NULL or the value of the node to be searched becomes equal
to that of currentNode:

a. Make parent point to currentNode.

b. If the value to be deleted is less than that of currentNode: Make currentNode point to its left child.

c. If the value to be deleted is greater than that of currentNode: Make currentNode point to its right child.

After executing the preceding algorithm, currentNode is positioned on the node to be deleted and parent is positioned on
the currentNode’s parent.

Once you locate the node to be deleted and its parent, there can be the following cases:

Case I - The node to be deleted is a leaf node: In this case, the left and right child fields of currentNode are NULL. The
following algorithm depicts the logic to implement a delete operation on a leaf node:
1. If currentNode is the root node: // If parent is NULL

a. Make ROOT point to NULL.

b. Go to step .

2. If currentNode is the left child of parent:

a. Make the left child field of parent point to NULL.

b. Go to step 4.

3. If currentNode is the right child of parent:

a. Make the right child field of parent point to NULL.

b. Go to step 4.

4. Release the memory for currentNode.

Consider the binary search tree, as shown in the following figure.

The Binary Search Tree Before Deleting the Leaf Node

Suppose you want to delete node 75. The process of deleting node 75 from the binary search tree is shown in the
following table.

Operations Illustration
Locate the node to be deleted. Mark it as
currentNode and its parent as parent.

If currentNode is the left child of parent,


make the left child field of parent point to
NULL.

Release the memory for

currentNode.

The Process of Deleting a Leaf Node from a Binary Search Tree

Case II - The node to be deleted has one child (left or right): In this case, currentNode can either have a left child or a right
child. Mark the only child of currentNode as child. To delete currentNode, the left/right child field of parent is linked with
the left/right child of currentNode. The following algorithm depicts the logic to implement a delete operation on a node
having one child:

1. If currentNode has a left child:

a. Mark the left child of currentNode as child.

b. Go to step 3.

2. If currentNode has a right child:

a. Mark the right child of currentNode as child.

b. Go to step 3.

3. If currentNode is the root node:

a. Mark child as ROOT.

b. Go to step 6.

4. If currentNode is the left child of parent:

a. Make left child field of parent point to child.

b. Go to step 6.

5. If currentNode is the right child of parent:

a. Make right child field of parent point to child.

b. Go to step 6.

6. Release the memory for currentNode.

Consider the binary search tree, as shown in the following figure.

The Binary Search Tree Before Deleting a Node Having One Child

Suppose you want to delete node 80. The process of deleting node 80 is shown in the following table.
Operation Illustration

Locate the node to be deleted. Mark it as


currentNode and its parent as parent. If
currentNode has a left child, mark the
left child of currentNode as child.

If currentNode is the right child of parent,


make right child field of parent point to
child.
Release the memory for currentNode.

The Process of Deleting a Node Having One Child from a Binary Search Tree

Case III - The node to be deleted has two children: In this case, currentNode has two children. To delete currentNode from
the tree, you need to replace it with its inorder successor. The inorder successor of a node x refers to the next node after x
in the inorder traversal of the tree. To locate the inorder successor of a node, you need to locate the left most node in its
right sub tree.

The following algorithm depicts the logic to implement the delete operation on a node having two children:

1. Locate the inorder successor of currentNode. Mark it as Inorder_suc. Execute the following steps to locate
Inorder_suc:

a. Mark the right child of currentNode as Inorder_suc.

b. Repeat until the left child of inorder_suc becomes NULL: Make Inorder suc point to its left child.

2. Replace the information held by currentNode with that of Inorder_suc.

3. If the node marked Inorder suc is a leaf node:

Delete the node marked Inorder_suc by using the algorithm for Case I.

4. If the node marked Inorder_suc has one child:

Delete the node marked Inorder_suc by using the algorithm for Case II.

Because the node marked Inorder_suc is the leftmost node of the right sub tree of currentNode, it cannot have a left child.
Therefore, the node marked Inorder_suc has, at the most, one child. This means that the node marked Inorder_suc can be
deleted by executing the algorithm that was developed for deleting a leaf node, or the one that was developed for deleting
a node with one child.
Consider the binary search tree, as shown in the following figure.

The Binary Search Tree Before Deleting a Node Having Two Children

Suppose you want to delete node 72. The process is shown in the following table.

Operation Illustration

Locate the node to be deleted. Mark it as


currentNode. Locate the inorder
successor of currentNode. The inorder
successor of currentNode will be the
leftmost node in the right sub tree of
currentNode. Mark the inorder successor
of currentNode as Inorder suc.
Replace the information held by
currentNode with that of Inorder_suc.

The node marked Inorder_suc is a leaf


node. Therefore, delete it by using the
algorithm for deleting a leaf node.

The Process of Deleting a Node Having Two Children from a Binary Search Tree

Activity 9: Implementing a Binary Search Tree


Problem Statement

Write a program to implement the insert and traverse operations on a binary search tree that contains the words in a
dictionary.

Solution in Java

To implement insert and traverse operations on a binary search tree, you need to perform the following tasks:

1. Write the code in NetBeans.

2. Execute the code and verify the output.

Task 1: Writing the Code in NetBeans

To write the code in NetBeans, you need to perform the following steps:

1. Open NetBeans.

2. Create a new project with the name, BinarySearchTree.


3. Locate the binarysearchtree package in the Projects window.

4. Add the Node.java file.

5. Replace all the existing code in the Node.java file with the following code:

package binarysearchtree;

/* The Node class consists of three data members, the information, reference

to the right child, and reference to the left child. */

public class Node {

public String info;

public Node lchild;

public Node rchild;

public Node(String i, Node l, Node r) /* Constructor for the Node class */

info = i;

lchild = l;

rchild = r;

6. Save the Node.java file.

7. Rename the BinarySearchTree.java file to BinaryTree.java.

8. Replace the code given in the BinaryTree.java file with the following code:

package binarysearchtree;

import java.util.Scanner;

public class BinaryTree {

public Node ROOT;


private Node parent,currentNode;

public BinaryTree()

ROOT = null; // Initializing ROOT to null

public void insert(String element) // Inserts a Node in the Binary Search


Tree

Node tmp;

parent=currentNode=null;

if (find(element)==true) /* Checks if the node to be inserted is already

present or not*/

System.out.println("Duplicate words not allowed");

return;

else // If the specified Node is not present

tmp = new Node(element, null, null); // Creates a Node

if (parent == null) // If the tree is empty

ROOT = tmp;

else

if (element.compareTo(parent.info) < 0)

parent.lchild = tmp;

else

parent.rchild = tmp;

}
}

public boolean find(String element)

/* This function finds a given element in the tree and returns a variable

containing the address of the

corresponding node. It also returns a variable containing the address of the

parent of the node. */

currentNode = ROOT;

parent = null;

while ((currentNode != null) && (!element.equals(currentNode.info)))

parent = currentNode;

if (element.compareTo(currentNode.info) < 0)

currentNode = currentNode.lchild;
}

else

currentNode = currentNode.rchild;

if(currentNode==null)

return false;

else

return true;

public void inorder(Node ptr) /* Performs the inorder traversal of the tree

*/

if (ptr != null)
{

inorder(ptr.lchild);

System.out.print(ptr.info + " ");

inorder(ptr.rchild);

public static void main(String[] args)

BinaryTree b = new BinaryTree();

while (true)

System.out.println("\nMenu");

System.out.println("1. Implement insert operation");

System.out.println("2. Perform inorder traversal");

System.out.println("3. Exit");

System.out.print("\nEnter your choice (1-3): ");

char ch = new Scanner(System.in).nextLine().charAt(0);

System.out.println();

switch (ch)

case '1':

System.out.print("Enter a word: ");

String word = new Scanner(System.in).nextLine();

b.insert(word);

break;

case '2':

if (b.ROOT == null)

System.out.println("Tree is empty");

new Scanner(System.in).nextLine();
return;

b.inorder(b.ROOT);

break;

case '3':

return;

default:

System.out.println("Invalid option");

break;

9. Save the project.

Task 2: Executing the Code and Verifying the Output

Graphs in Data Structure

Graphs in data structures are non-linear data structures made up of a finite number of nodes or vertices and the edges
that connect them. Graphs in data structures are used to address real-world problems in which it represents the problem
area as a network like telephone networks, circuit networks, and social networks. For example, it can represent a single
user as nodes or vertices in a telephone network, while the link between them via telephone represents edges.

What Are Graphs in Data Structure?

A graph is a non-linear kind of data structure made up of nodes or vertices and edges. The edges connect any two nodes in
the graph, and the nodes are also known as vertices.
This graph has a set of vertices V= { 1,2,3,4,5} and a set of edges E= { (1,2),(1,3),(2,3),(2,4),(2,5),(3,5),(4,50 }.

Now that you’ve learned about the definition of graphs in data structures, you will learn about their various types.

Types of Graphs in Data Structures

There are different types of graphs in data structures, each of which is detailed below.

1. Finite Graph

The graph G=(V, E) is called a finite graph if the number of vertices and edges in the graph is limited in number
2. Infinite Graph

The graph G=(V, E) is called a finite graph if the number of vertices and edges in the graph is interminable.

3. Trivial Graph

A graph G= (V, E) is trivial if it contains only a single vertex and no edges.


4. Simple Graph

If each pair of nodes or vertices in a graph G=(V, E) has only one edge, it is a simple graph. As a result, there is just one
edge linking two vertices, depicting one-to-one interactions between two elements.

5. Multi Graph

If there are numerous edges between a pair of vertices in a graph G= (V, E), the graph is referred to as a multigraph. There
are no self-loops in a Multigraph.
6. Null Graph

It's a reworked version of a trivial graph. If several vertices but no edges connect them, a graph G= (V, E) is a null graph.

7. Complete Graph

If a graph G= (V, E) is also a simple graph, it is complete. Using the edges, with n number of vertices must be connected. It's
also known as a full graph because each vertex's degree must be n-1.
8. Pseudo Graph

If a graph G= (V, E) contains a self-loop besides other edges, it is a pseudograph.

9. Regular Graph

If a graph G= (V, E) is a simple graph with the same degree at each vertex, it is a regular graph. As a result, every whole
graph is a regular graph.
10. Weighted Graph

A graph G= (V, E) is called a labeled or weighted graph because each edge has a value or weight representing the cost of
traversing that edge.

11. Directed Graph

A directed graph also referred to as a digraph, is a set of nodes connected by edges, each with a direction.
12. Undirected Graph

An undirected graph comprises a set of nodes and links connecting them. The order of the two connected vertices is
irrelevant and has no direction. You can form an undirected graph with a finite number of vertices and edges.

13. Connected Graph

If there is a path between one vertex of a graph data structure and any other vertex, the graph is connected.
14. Disconnected Graph

When there is no edge linking the vertices, you refer to the null graph as a disconnected graph.

15. Cyclic Graph

If a graph contains at least one graph cycle, it is considered to be cyclic.


16. Acyclic Graph

When there are no cycles in a graph, it is called an acyclic graph.

17. Directed Acyclic Graph

It's also known as a directed acyclic graph (DAG), and it's a graph with directed edges but no cycle. It represents the edges
using an ordered pair of vertices since it directs the vertices and stores some data.
18. Subgraph

The vertices and edges of a graph that are subsets of another graph are known as a subgraph.

Terminologies of Graphs in Data Structures

Following are the basic terminologies of graphs in data structures:

• An edge is one of the two primary units used to form graphs. Each edge has two ends, which are vertices to which
it is attached.
• If two vertices are endpoints of the same edge, they are adjacent.
• A vertex's outgoing edges are directed edges that point to the origin.
• A vertex's incoming edges are directed edges that point to the vertex's destination.
• The total number of edges occurring to a vertex in a graph is its degree.
• The out-degree of a vertex in a directed graph is the total number of outgoing edges, whereas the in-degree is the
total number of incoming edges.
• A vertex with an in-degree of zero is referred to as a source vertex, while one with an out-degree of zero is known
as sink vertex.
• An isolated vertex is a zero-degree vertex that is not an edge's endpoint.
• A path is a set of alternating vertices and edges, with each vertex connected by an edge.
• The path that starts and finishes at the same vertex is known as a cycle.
• A path with unique vertices is called a simple path.
• For each pair of vertices x, y, a graph is strongly connected if it contains a directed path from x to y and a directed
path from y to x.
• A directed graph is weakly connected if all of its directed edges are replaced with undirected edges, resulting in a
connected graph. A weakly linked graph's vertices have at least one out-degree or in-degree.
• A tree is a connected forest. The primary form of the tree is called a rooted tree, which is a free tree.
• A spanning subgraph that is also a tree is known as a spanning tree.
• A connected component is the unconnected graph's most connected subgraph.
• A bridge, which is an edge of removal, would sever the graph.
• Forest is a graph without a cycle.

Graph Traversal Algorithms

Graph traversal, a subset of tree traversal, is the process of visiting or updating the vertex in a graph. The two techniques
for graph traversal algorithms are as follows:

• Depth-First Search
o This search technique explores data structures like trees and graphs. It begins at the root node and
continues with the examination as far as possible before backtracking. A stack is required to keep track of
the child nodes that have been encountered but not inspected.

• Breadth-First Search
o This search technique begins at the graph root and examines all nodes at one depth level before moving
on to the next depth level. A queue is required to keep track of the child nodes that have been
encountered but not inspected.

Breadth First Search (BFS) for a Graph:

Breadth First Search (BFS) is a graph traversal algorithm that explores all the vertices in a graph at the current depth before
moving on to the vertices at the next depth level. It starts at a specified vertex and visits all its neighbors before moving on
to the next level of neighbors. BFS is commonly used in algorithms for pathfinding, connected components, and shortest
path problems in graphs.
Breadth First Search (BFS) for a Graph Algorithm:

Let’s discuss the algorithm for the BFS:

1. Initialization: Enqueue the starting node into a queue and mark it as visited.
2. Exploration: While the queue is not empty:
▪ Dequeue a node from the queue and visit it (e.g., print its value).
▪ For each unvisited neighbor of the dequeued node:
a. Enqueue the neighbor into the queue.
b. Mark the neighbor as visited.
3. Termination: Repeat step 2 until the queue is empty.
This algorithm ensures that all nodes in the graph are visited in a breadth-first manner, starting from the starting node.

How Does the BFS Algorithm Work?

Starting from the root, all the nodes at a particular level are visited first and then the nodes of the next level are
traversed till all the nodes are visited.

To do this a queue is used. All the adjacent unvisited nodes of the current level are pushed into the queue and the
nodes of the current level are marked visited and popped from the queue.

Illustration:

Let us understand the workings of the algorithm with the help of the following example.

Step 1: Initially queue and visited arrays are empty.

Queue and visited arrays are empty initially.

Step 2: Push node 0 into a queue and mark it visited.


Push node 0 into a queue and mark it visited.

Step 3: Remove node 0 from the front of the queue and visit the unvisited neighbours and push them into the queue.

Remove node 0 from the front of the queue and visit the unvisited neighbours and push into the queue.

Step 4: Remove node 1 from the front of queue and visit the unvisited neighbours and push them into queue.

Remove node 1 from the front of queue and visited the unvisited neighbours and push
Step 5: Remove node 2 from the front of queue and visit the unvisited neighbours and push them into queue.

Remove node 2 from


the front of queue and visit the unvisited neighbours and push them into queue.

Step 6: Remove node 3 from the front of queue and visit the unvisited neighbours and push them into queue.

As we can see that every neighbours of node 3 is visited, so move to the next node that are in the front of the queue.

Remove node 3 from


the front of queue and visit the unvisited neighbours and push them into queue.

Steps 7: Remove node 4 from the front of queue and visit the unvisited neighbours and push them into queue.

As we can see that every neighbours of node 4 are visited, so move to the next node that is in the front of the queue.
Remove node 4 from
the front of the queue and visit the unvisited neighbours and push them into the queue.

Now, the Queue becomes empty, So, terminate these process of iteration.

How does DFS work?

Depth-first search is an algorithm for traversing or searching tree or graph data structures. The algorithm starts at the root
node (selecting some arbitrary node as the root node in the case of a graph) and explores as far as possible along each
branch before backtracking.

Let us understand the working of Depth First Search with the help of the following illustration:

Step 1: Initially stack and visited arrays are empty.

Stack and visited arrays are empty initially.


Step 2: Visit 0 and put its adjacent nodes which are not visited yet into the stack.

Visit node 0 and put its adjacent nodes (1, 2, 3) into the stack

Step 3: Now, Node 1 at the top of the stack, so visit node 1 and pop it from the stack and put all of its adjacent nodes
which are not visited in the stack.

Visit node 1

Step 4: Now, Node 2 at the top of the stack, so visit node 2 and pop it from the stack and put all of its adjacent nodes
which are not visited (i.e, 3, 4) in the stack.
Visit node 2 and put its unvisited adjacent nodes (3, 4) into the stack

Step 5: Now, Node 4 at the top of the stack, so visit node 4 and pop it from the stack and put all of its adjacent nodes
which are not visited in the stack.

Visit node 4

Step 6: Now, Node 3 at the top of the stack, so visit node 3 and pop it from the stack and put all of its adjacent nodes
which are not visited in the stack.

Visit node 3

Now, Stack becomes empty, which means we have visited all the nodes and our DFS traversal ends.

You might also like