DataStructureandPseudocode
DataStructureandPseudocode
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
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
10 natural numbers:
2. Display counter.
3. Increment counter by 1.
The preceding step-by-step procedure is an algorithm because it produces the correct result with 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.
• 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.
• 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.
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.
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.
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:
max = num2
max = num3
max = num4
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.
Perform the same comparison and update process for num3 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.
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:
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:
After iterating through all elements, the final value of "max" will represent the maximum value within the array.
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.
• 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.
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:
a. Set rem=n%10.
c. Set n=n/10.
4. If temp=arm, then:
Else:
To check whether the given number is an Armstrong number, you need to perform the following tasks:
Code:
package armstrongnumber;
import java.util.*;
System.out.println("Enter a number");
temp = num;
while (num > 0)
if (temp == arm)
else
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.
int n = 10;
System.out.println(n);
The above source code is converted into a pseudo-code to understand in a better way.
Activity P.1: Implementing Pseudocode Based on the Algorithm for Calculating the Factorial of a Given Number
Step 1: start
Step 8: stop
Display fact
End program
Activity P.2: Implement Algorithm and Pseudocode to find the Maximum Number in an Array:
Algorithm:
After iterating through the array, max will contain the maximum element.
Pseudocode:
procedure findMax(arr)
max = arr[0]
max = element
return max
end procedure
Algorithm:
Pseudocode:
procedure isPrime(num)
if num <= 1
return false
if num % i == 0
return false
return true
end procedure
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.
f(n) = 1 + 2 + 3 + 4 + 5 +...+ n
f(n) = f(n – 1) + n
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
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!)
3!=(3×(2×1!))
3!=(3×(2×(1×0!)))
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)
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.
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 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
The following algorithm can be used to move the top n discs from the first pin, START, to the final pin,
1. When n = 1:
2. Move the top n – 1 discs from START to TEMP using FINISH as an intermediary [MOVE (n – 1, START, FINISH,
TEMP)]
4. Move the top n – 1 discs from TEMP to FINISH using START as an intermediary [MOVE (n – 1, TEMP, START, FINISH)]
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.
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.
There are various sorting algorithms that are used to sort data. Some of these are:
• Bubble sort
• Insertion sort
• Quick 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.
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.
There are five elements in the list. Therefore, four passes will be required to sort the list.
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.
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.
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.
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.
1. Set pass = 1.
4. Increment pass by 1.
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
where,
d is the step value, which is the difference in successive terms of an arithmetic progression.
n=n–1
a=1
d=1
Therefore,
Sum = (n – 1)/2 [2 × 1 + (n – 1 – 1) × 1]
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.
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:
To write the code in NetBeans, you need to perform the following steps:
1. Open NetBeans.
3. Replace the code given in the ListBubbleSort.java file with the following code:
package listbubblesort;
import java.util.*;
private int n;
while (true)
n = Integer.parseInt(s);
{
break;
else if (n < 0)
System.out.println("");
System.out.println("-----------------------");
System.out.println("-----------------------");
a[i] = Integer.parseInt(s1);
System.out.println("");
System.out.println("-----------------------");
System.out.println("-----------------------");
for (int j = 0; j < n; j++)
System.out.println(a[j]);
int temp;
temp = a[j];
a[j + 1] = temp;
}
public static void main(String[] arg)
myList.BubbleSort();
myList.display();
new Scanner(System.in).nextLine();
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.
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.
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 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 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 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 unsorted list is now empty, and the sorted list contains all the elements. This means that the list is now completely
sorted.
3. Set j = i – 1.
4. Repeat until j becomes less than 0 or arr[j] becomes less than or equal to temp:
b. Decrement j by 1.
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).
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:
To write the code in NetBeans, you need to perform the following steps:
1. Open NetBeans.
3. Replace the code given in the ListInsertionSort.java file with the following code:
package listinsertionsort;
import java.util.*;
private int n;
while (true)
n = Integer.parseInt(s);
break;
else if (n < 0)
System.out.println("");
System.out.println("-----------------------");
System.out.println("-----------------------");
a[i] = Integer.parseInt(s1);
}
System.out.println("");
System.out.println("-----------------------");
System.out.println("-----------------------");
System.out.println(a[j]);
int j = i - 1;
a[j + 1] = a[j];
j = j - 1;
a[j + 1] = temp;
myList.read();
myList.InsertionSort();
myList.display();
System.exit(0);
Various sorting algorithms, such as bubble sort and insertion sort, are useful for sorting small lists of data.
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.
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.
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.
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.
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.
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.
6. Interchange the pivot value with the last element of List 1, as shown in the following figure.
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.
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.
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.
12. Interchange the pivot value with the last element of Sublist 1, as shown in the following figure.
The pivot value, 46, has now reached its correct position in the list. Now, you need to sort Sublist 1 and Sublist 2.
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 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:
//the list and high is the index of the last //element in the list
1. If (low > high): Return.
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
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:
Swap arr[low] with arr[j]. // Swap the pivot with last element in
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 */
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:
To write the code in NetBeans, you need to perform the following steps:
1. Open NetBeans.
package listquicksort;
import java.util.*;
private int n;
public ListQuickSort()
cmp_count = 0;
mov_count = 0;
while (true)
n = Integer.parseInt(s);
break;
}
else if (n < 0)
System.out.println("\n-----------------------");
System.out.println("-----------------------");
arr[i] = Integer.parseInt(s1);
int temp;
temp = arr[x];
arr[x] = arr[y];
arr[y] = temp;
int pivot, i, j;
return;
}
i = low + 1;
j = high;
pivot = arr[low];
while (i <= j)
i++;
cmp_count++;
cmp_count++;
j--;
cmp_count++;
cmp_count++;
at index j */
swap(i, j);
mov_count++;
}
sorted list. */
if (low < j)
mov_count++;
q_sort(low, j - 1);
q_sort(j + 1, high);
System.out.println("\n-----------------------");
System.out.println("-----------------------");
System.out.println(arr[j]);
return (n);
}
{
// Declaring the object of the class
myList.read();
myList.display();
System.exit(0);
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.
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.
4. Increment i by 1.
5. If i = n:
Display “Found”.
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.
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:
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.*;
int i;
while (true)
n = Integer.parseInt(s);
break;
else
elements.\n");
System.out.println("");
System.out.println("-----------------------");
System.out.println(" Enter array elements ");
System.out.println("-----------------------");
arr[i] = Integer.parseInt(s1);
do
ctr = 0;
ctr++;
if (arr[i] == item)
break;
if (i == n)
ch = new Scanner(System.in).nextLine().charAt(0);
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.
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:
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.
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.
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 = (0 + 3)/2
Mid = 1
Therefore, the middle element will be at the index, 1, as shown in the following figure.
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:
2. Set lowerbound = 0.
3. Set upperbound = n – 1.
a) Display “Found”.
b) Go to step 10.
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
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).
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:
To write the code in NetBeans, you need to perform the following steps:
1. Open NetBeans.
package binarysearch;
import java.util.*;
int i;
while (true)
n = Integer.parseInt(s);
break;
else
elements.\n");
System.out.println("");
System.out.println("--------------------------------");
arr[i] = Integer.parseInt(s1);
do
int lowerbound = 0;
int upperbound = n - 1;
lowerbound = mid + 1;
else
upperbound = mid - 1;
}
mid = (lowerbound + upperbound) / 2;
ctr++;
if (item == arr[mid])
else
ch = new Scanner(System.in).nextLine().charAt(0);
} while ((ch == 'y') || (ch == 'Y'));
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.
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.
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.
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.
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.
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.
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.
The various operations implemented on a linked list are insert, delete, traverse, and search.
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 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.
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
}
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
List()
START = null;
/* statements */
/* statements */
/* statements */
/* 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:
The process of inserting a node in an empty list is illustrated in the following table.
Operation Illustration
If the linked list is not empty, you may need to insert a node at any of the following positions 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.
The following algorithm depicts the logic to insert a node at the beginning of the linked list:
3. Make the next field of the new node point to START (that is the first node in the list).
The process of inserting a node at the beginning of a list is illustrated in the following table.
Operation Illustration
The following algorithm depicts the logic to insert a node at the end of the linked list:
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:
The process of inserting a node at the end of a list is illustrated in the following table.
Operation Illustration
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.
The following algorithm is the modified algorithm that depicts the logic to insert a node at the end of the list:
c. Go to the step, 6.
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:
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:
c. Repeat the steps, d and e, until current.info becomes greater than newnode.info or current becomes equal to
NULL.
b. Exit.
The process of inserting the node, 16, in the preceding list is illustrated in the following table.
Operation Illustration
current.
Make the next field of the
new node point to current.
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:
The following algorithm depicts the logic to delete a node from the beginning of a list:
The process of deleting a node from the beginning of a list is illustrated in the following table.
Operation Illustration
current.
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:
c. Repeat the steps, d and e, until the value of current matches the value to be deleted or current becomes NULL.
2. If current is NULL:
a. Display “Value not found in list.”.
b. Exit.
b. Go to the step, 5.
The process of deleting a node between two nodes in a list is illustrated in the following table.
Operation Illustration
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:
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:
To write the code in NetBeans, you need to perform the following steps:
1. Open NetBeans.
5. Replace the existing code in the Node.java file with the following code:
package implementing_singly_linked_lists;
8. Replace the code given in the SinglyLinkedList.java file with the following code:
package implementing_singly_linked_lists;
import java.util.*;
public SinglyLinkedList ()
START = null;
int rollNo;
String nm;
nm = new Scanner(System.in).nextLine();
newnode.rollNumber = rollNo;
newnode.name = nm;
previous = null;
current = START;
/* Scan through the linked list until you find a node whose roll number is
if (rollNo == current.rollNumber)
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 */
previous=null;
current = START;
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;
/* Scan the linked list to search for the specified roll number */
current = current.next;
return current;
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)
"\n");
System.out.println();
if (START == null)
return true;
else
return false;
while (true)
try
System.out.println("\nMenu");
System.out.println("1. Add a record to the list");
System.out.println("5. Exit");
switch (ch)
case '1':
obj.addNode();
break;
case '2':
if (obj.listEmpty())
System.out.println("\nList is empty");
break;
Scanner(System.in).nextLine());
System.out.println();
if (obj.delNode(rollNo) == false)
else
break;
case '3':
obj.traverse();
break;
case '4':
if (obj.listEmpty() == true)
System.out.println("\nList is empty");
break;
to be searched: ");
Node result=obj.Search(num);
if ( result== null)
else
System.out.println("\nRecord found");
break;
case '5':
System.exit(0);
default:
System.out.println("\nInvalid option");
break;
}
catch (RuntimeException e)
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.
A class that represents a node in a doubly-linked list: In a doubly-linked list, each node needs to store:
• Information
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.
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:
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:
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:
Once the first node is inserted, the subsequent nodes can be inserted at any of the following positions:
Now, you will learn to write algorithms for inserting a node at the various positions in a linked list.
The following algorithm depicts the logic for inserting a node at the beginning of a doubly-linked list:
3. Make the next field of the new node point to the first node 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:
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:
c. Repeat the steps, d and e, until current.info > newnode.info or current = NULL.
d. Make previous point to current.
9. If previous is NULL:
The process of inserting a node between two nodes in a doubly-linked list is illustrated in the following table.
Operation Illustration
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:
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:
c. Repeat the steps, d and e, until current.info > newnode.info or current = NULL.
9. If previous is NULL:
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
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:
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.
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:
3. If START is not NULL(if the deleted node was not the only node in the list), then:
The process of deleting a node from the beginning of the doubly-linked list is illustrated in the following table.
Operation Illustration
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:
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.
2. If current is NULL:
b. Exit.
4. If previous is NULL:
The process of deleting a node between two nodes in the doubly-linked list is illustrated in the following table.
Operation Illustration
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:
2. If current is NULL:
b. Exit.
4. If previous is NULL:
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.
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.
A Stack of Books
• Data cannot be deleted from the middle of the stack. All the items from the top first need to be removed.
• 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.
Pop book 3 from the stack Invalid Operation. To pop book 3, you need to
first pop book 4 from the 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
{
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;
// Statements
// Statements
// Statements
}
After declaring the class to implement the operations on a stack, you need to implement the PUSH and POP operations.
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:
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:
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:
b. Exit.
Problem Statement
Solution in Java
To implement a stack by using a linked list, you need to perform the following tasks:
To write the code in NetBeans, you need to perform the following steps:
1. Open NetBeans.
5. Replace all the existing code in the Node.java file with the following code:
package stack_implementation;
stack
contents
info = i;
next = n;
}
6. Save the Node.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.*;
top = null;
if (top == null)
return (true);
else
return (false);
node
fresh.next = top; /* Make next field of the new node point to the
sequence */
Node tmp;
System.out.println("\nStack Empty");
else
System.out.println("\nStack Elements:");
System.out.println(tmp.info);
System.out.println();
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");
switch (ch)
case '1':
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;
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.
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.
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.
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.
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
}
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
public LinkedQueue()
FRONT = null;
REAR = null;
After deciding the representation for the linked queue, you can start writing algorithms for inserting and deleting elements
in the 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:
c. Exit.
The process of inserting an element in a linked queue is shown in the following table.
Operation Illustration
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:
b. Exit.
2. Mark the node marked FRONT as current.
The process of deleting an element from a linked queue is shown in the following table.
Operation Illustration
Problem Statement
Solution in Java
To implement insert and delete operations on a linked queue, you need to perform the following tasks:
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.
5. Replace all the existing code in the Node.java file with the following code:
package implement_queue;
data = d;
n = next;
8. Replace the code given in the Queue.java file with the following code:
package implement_queue;
import java.util.*;
private Node FRONT, REAR; /* Pointers to the first and last nodes of the
queue */
public Queue()
FRONT = null;
REAR = null;
}
public void insert(int element) // Inserts a node in the queue
Node newnode;
if (FRONT == null)
/* If the queue is empty, then both FRONT and REAR should point to
FRONT = newnode;
REAR = newnode;
else
/* If the queue is NOT empty, then REAR points to the last node.
REAR.next = newnode;
REAR = newnode;
queue\n");
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.
Node tmp;
System.out.println("Queue is empty");
return;
else
for (tmp = FRONT; tmp != null; tmp = tmp.next) /* traverse the queue
System.out.println();
while (true)
try
{
System.out.println("\nMenu");
System.out.println("4. Exit");
System.out.println();
switch (ch)
case '1':
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());
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.
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.
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.
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
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,
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.
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.
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.
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.
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.
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.
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:
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
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 BinaryTree()
ROOT = null;
};
Consider the linked representation of a binary tree, as shown in the following figure.
The Linked Representation of 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.
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:
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.
The following figure depicts the sequence of traversal in the inorder traversal.
The Inorder Traversal
1 If (root = NULL):
Exit.
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:
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.
The following figure depicts the sequence of traversal in the preorder traversal.
The Preorder Traversal
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:
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.
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.
The following figure depicts the sequence of traversal in the postorder traversal.
The Postorder Traversal
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.
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.
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:
2. If currentNode is NULL:
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:
i. Display “Found”.
ii. Exit.
ii. Go to step 2.
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.
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.
of 52.
child of 52.
The following algorithm depicts the logic of inserting a node in a binary search tree:
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:
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.
b. Exit.
6. If the value in the data field of the new node is less than or equal to that of parent:
7. If the value in the data field of the new node is greater than that of the parent:
b. Exit.
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
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:
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:
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
b. Go to step .
b. Go to step 4.
b. Go to step 4.
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.
currentNode.
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:
b. Go to step 3.
b. Go to step 3.
b. Go to step 6.
b. Go to step 6.
b. Go to step 6.
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
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:
b. Repeat until the left child of inorder_suc becomes NULL: Make Inorder suc point to its left child.
Delete the node marked Inorder_suc by using the algorithm for Case I.
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
The Process of Deleting a Node Having Two Children from a Binary Search Tree
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:
To write the code in NetBeans, you need to perform the following steps:
1. Open NetBeans.
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
info = i;
lchild = l;
rchild = r;
8. Replace the code given in the BinaryTree.java file with the following code:
package binarysearchtree;
import java.util.Scanner;
public BinaryTree()
Node tmp;
parent=currentNode=null;
present or not*/
return;
ROOT = tmp;
else
if (element.compareTo(parent.info) < 0)
parent.lchild = tmp;
else
parent.rchild = tmp;
}
}
/* This function finds a given element in the tree and returns a variable
currentNode = ROOT;
parent = null;
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);
inorder(ptr.rchild);
while (true)
System.out.println("\nMenu");
System.out.println("3. Exit");
System.out.println();
switch (ch)
case '1':
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;
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.
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.
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
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
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.
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.
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.
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.
• 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, 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) 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:
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.
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 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.
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.
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.
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:
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.