Dsa Asm Compress
Dsa Asm Compress
Unit number and title Unit 19: Data Structures and Algorithms
Plagiarism
Plagiarism is a particular form of cheating. Plagiarism must be avoided at all costs and students who break the rules, however innocently, may be
penalised. It is your responsibility to ensure that you understand correct referencing practices. As a university level student, you are expected to
use appropriate references throughout and keep carefully detailed notes of all your sources of materials for material you have used in your work,
including any material downloaded from the Internet. Please consult the relevant unit lecturer or your course tutor if you need any further advice.
Student Declaration
I certify that the assignment submission is entirely my own work and I fully understand the consequences of plagiarism. I declare that the work
submitted for assessment has been carried out without assistance other than that which is acceptable according to the rules of the specification. I
certify I have clearly referenced any sources and any artificial intelligence (AI) tools used in the work. I understand that making a false declaration is
a form of malpractice.
Student’s signature
Grading grid
P1 P2 P3 P4 P5 P6 P7 M1 M2 M3 M4 M5 D1 D2 D3 D4
Summative Feedback: Resubmission Feedback:
2
TABLE OF CONTENT
TABLE OF CONTENT...................................................................................................................................... 3
TABLE OF FIGURE.......................................................................................................................................... 5
LIST OF TABLE............................................................................................................................................... 5
INTRODUCE.................................................................................................................................................. 5
CHAPTER 1: Examine abstract data types, concrete data structures and algorithms (LO1).........................6
1.1 . Create a design specification for data structures, explaining the valid operations that can be
carried out on the structures.(P1).............................................................................................................6
1.1.1 Definition and Role of Abstract Data Types (ADTs)......................................................................6
1.1.2 Description of Each Data Structure: Stack, Queue, Linked List.....................................................8
1.1.3 Specific Illustrative Examples......................................................................................................11
1.1.4 Analysis of Operations................................................................................................................15
1.2 Determine the operations of a memory stack and how it is used to implement function calls in a
computer.(P2)......................................................................................................................................... 16
1.2.1 Overview of the memory stack.................................................................................................. 16
1.2.2 How the Memory Stack Works...................................................................................................18
1.2.3 Stack and Recursion................................................................................................................... 18
1.2.4 Advantages and Disadvantages of the Memory Stack................................................................20
1.2.5 Visual Illustration of the Memory Stack.....................................................................................22
1.3 Illustrate, with an example, a concrete data structure for a First in First out (FIFO) queue. (M1)....24
1.3.1 Overview of the FIFO Queue...................................................................................................... 24
1.3.2 Basic Operations of a Queue......................................................................................................25
1.3.3 Implementing a Queue Using an Array.......................................................................................25
1.3.4 Implementing a Queue Using a Linked List.................................................................................31
1.3.5 Comparison of the Two Implementations..................................................................................35
1.4 Compare the performance of two sorting algorithms.(M2)..............................................................38
1.4.1 Overview of Sorting Algorithms..................................................................................................38
1.4.2 Description of the Two Algorithms.............................................................................................39
1.4.3 Comparison of the Two Algorithms: Insertion Sort and Selection Sort......................................42
3
1.4.4 Practical Testing......................................................................................................................... 44
CHAPTER 2: Specify abstract data types and algorithms in a formal notation (LO2)..................................48
2.1 Specify the abstract data type for a software stack using an imperative definition.(P3)..................48
2.1.1 Overview of ADT and Imperative Definition...............................................................................48
2.1.2 Example of Defining an ADT Using the Imperative Approach.....................................................48
2.1.3 Implementation of the Example Using Pseudocode or Java.......................................................50
2.1.4 Advantages of Imperative Definition..........................................................................................54
2.1.5 Practical Application of the Stack ADT........................................................................................55
2.2 Examine the advantages of encapsulation and information hiding when using an ADT.(M3)..........56
2.2.1 Definition of Concepts................................................................................................................56
2.2.2 Benefits of Encapsulation and Information Hiding When Using ADTs........................................57
2.2.3 Illustrative Example.................................................................................................................... 58
2.2.4 Comparison Before and After Applying Encapsulation and Information Hiding.........................61
CHAPTER 3: Implement complex data structures and algorithms (L03).....................................................64
3.1 Implement a complex ADT and algorithm in an executable programming language to solve a well-
defined problem. (P4)............................................................................................................................. 64
3.1.1 Selection of a Specific Problem.................................................................................................. 64
3.1.2 ADT and Algorithm Used............................................................................................................ 65
3.1.3 Program Implementation...........................................................................................................67
3.1.4 Implementation Analysis............................................................................................................72
3.1.5 Results and Testing.....................................................................................................................73
3.2 Implement error handling and report test results. (P5)....................................................................74
3.2.1 Error Handling............................................................................................................................ 74
3.2.2 Test Results Reporting................................................................................................................77
3.3 Demonstrate How the Implementation of an ADT/Algorithm Solves a Well-Defined Problem (M4)
................................................................................................................................................................ 82
3.3.1 Problem Definition..................................................................................................................... 83
3.3.2 Solution Implementation............................................................................................................83
3.3.3 Solution Analysis.........................................................................................................................88
3.3.4 Test Run Results......................................................................................................................... 90
4
CHAPTER 4: Assess the effectiveness of data structures and algorithms (L04)..........................................95
4.1 Discuss how asymptotic analysis can be used to assess the effectiveness of an algorithm. (P6)......95
4.1.1 What is Asymptotic Analysis?.....................................................................................................95
4.1.2 Asymptotic Notations.................................................................................................................96
4.1.3 Asymptotic Analysis Process.......................................................................................................97
4.1.4 Illustrative Example.................................................................................................................... 99
4.1.5 Benefits of Asymptotic Analysis................................................................................................101
4.1.6 Practical Applications of Asymptotic Analysis..........................................................................103
4.1.7 How to Present Asymptotic Analysis in a Report......................................................................104
4.2 Determine two ways in which the efficiency of an algorithm can be measured, illustrating your
answer with an example.(P7)................................................................................................................105
4.2.1 What is Algorithm Efficiency?...................................................................................................105
4.2.2 Measuring Execution Time.......................................................................................................106
4.2.3 Measuring Space Usage............................................................................................................108
4.2.4 Illustrative Comparison Using Graphs......................................................................................110
4.3 Interpret what a trade-off is when specifying an ADT, using an example to support your answer.
(M5)...................................................................................................................................................... 112
4.3.1 Definition and Meaning of Trade-offs in ADTs..........................................................................112
4.3.2 Factors to Consider When Making Trade-offs in ADTs.............................................................113
4.3.3 Illustrating Trade-offs Through Examples.................................................................................114
4.3.4 Implementation Example: ArrayList vs. LinkedList...................................................................115
4.3.4.1 Using ArrayList for a To-Do List..........................................................................................115
4.3.4.2 Using LinkedList for a To-Do List........................................................................................116
4.3.4.3 Measuring Execution Time for Both Implementations......................................................117
4.3.5 Evaluation of Trade-offs........................................................................................................... 121
CONCLUSION............................................................................................................................................ 122
EVALUATION............................................................................................................................................. 123
REFERENCE............................................................................................................................................... 124
5
TABLE OF FIGURE
Figure 1-1: Abstract Data Type (ADT)......................................................................................................... 11
Figure 1-2: Stack......................................................................................................................................... 12
Figure 1-3: Queue....................................................................................................................................... 13
Figure 1-4: Linked List................................................................................................................................. 14
Figure 1-5: Source code of Stack example..................................................................................................16
Figure 1-6: Output of Stack example code................................................................................................. 16
Figure 1-7: Source code of Queue example................................................................................................17
Figure 1-8: Output of Queue example code...............................................................................................17
Figure 1-9: Source code of Linked List example..........................................................................................19
Figure 1-10: Output of Linked List example code.......................................................................................19
Figure 1-11: Memory stack.........................................................................................................................21
Figure 1-12: Recursion................................................................................................................................ 23
Figure 1-13: Example of recursion..............................................................................................................24
Figure 1-14: Output of recursion example..................................................................................................24
Figure 1-15: Stack Visualization..................................................................................................................27
Figure 1-16: FIFO Queue.............................................................................................................................28
Figure 1-17: Example Implementation Queue using array (part 1)............................................................33
Figure 1-18: Example Implementation Queue using array (part 2)............................................................33
Figure 1-19: Output of Queue implementation..........................................................................................34
Figure 1-20: Example Implementation Queue using LinkedList (part 1).....................................................38
Figure 1-21: Example Implementation Queue using LinkedList (part 2).....................................................38
Figure 1-22: Output of implementation Queue using LinkedList................................................................39
Figure 1-23: Sorting algorithms.................................................................................................................. 42
Figure 1-24: Insertion Sort..........................................................................................................................44
Figure 1-25: Selection Sort......................................................................................................................... 45
Figure 1-26: Code to measure time of 2 algorithm (part 1)........................................................................49
Figure 1-27: Code to measure time of 2 algorithm ( part 2).......................................................................50
Figure 1-28: Measure time of 2 algorithm output......................................................................................51
6
Figure 2-1: Java code to implement the stack ADT.....................................................................................56
Figure 2-2: Output of implement the stack ADT.........................................................................................57
Figure 2-3: Encapsulation........................................................................................................................... 60
Figure 2-4: Information hiding....................................................................................................................61
Figure 2-5: Illustrative Example.................................................................................................................. 64
Fifure 2-6: Output of Illustrative Example.................................................................................................. 64
Figure 2-7: Before Applying Encapsulation and Information Hiding...........................................................65
Figure 2-8: Direct Access............................................................................................................................ 66
Figure 2-9: After Applying Encapsulation and Information Hiding..............................................................67
Figure 3-1: student list is implemented using an ArrayList<Student>........................................................69
Figure 3-2: Sorting Algorithm..................................................................................................................... 70
Figure 3-3: Searching Algorithm................................................................................................................. 70
Figure 3-4: Ranking Algorithm.................................................................................................................... 71
Figure 3-5: Encapsulation of program........................................................................................................ 72
Figure 3-6: Abstraction of program............................................................................................................ 72
Figure 3-7: Main.java class......................................................................................................................... 73
Figure 3-8: ArrayList<Student> to store student........................................................................................73
Figure 3-9: Student’s attributes..................................................................................................................74
Figure 3-10: Menu-driven approach to interact with the user...................................................................76
Figure 3-11: Preventing Invalid Input for Numerical Values.......................................................................78
Fifure 3-12: Preventing Duplicate Student IDs code...................................................................................79
Figure 3-13: Input to check Preventing Duplicate Student IDs...................................................................79
Figure 3-14: Check Preventing Duplicate Student IDs output.....................................................................79
Figure 3-15: Handling Student Not Found Errors code (part 1)..................................................................80
Figure 3-16: Handling Student Not Found Errors code (part 2)..................................................................80
Figure 3-17: Handling Empty Student List Errors code...............................................................................80
Figure 3-18: Preventing Invalid Menu Choices code...................................................................................81
Figue 3-19: Test Case: Entering non-numeric values for marks..................................................................81
Figure 3-20: Expected Output of test Case: Entering non-numeric values for marks.................................82
Figure 2-21: Actual output of test Case: Entering non-numeric values for marks......................................82
7
Figure 3-22: Test Case: Adding a student with an existing ID.....................................................................82
Figure 3-23: Expected output of test Case: Adding a student with an existing ID......................................82
Figure 3-24:Actual output of test Case: Adding a student with an existing ID............................................82
Figure 3-25: Input of Test Case 1: Searching for an existing student..........................................................83
Figure 3-26:Expected output of Test Case 1: Searching for an existing student.........................................83
Figure 3-27: Actual output of Test Case 1: Searching for an existing student............................................83
Figure 3-28: Input of Test Case 2: Searching for a non-existent student....................................................83
Figure 3-29: Expected output of Test Case 2: Searching for a non-existent student..................................83
Figure 3-30: Actual output of Test Case 2: Searching for a non-existent student.......................................84
Figure 3-31: Test Case 1: Sorting students based on their average marks (Before Sorting).......................84
Figure 3-32: Test Case 1: Sorting students based on their average marks (After Sorting)..........................84
Figure 3-33: Input of Test Case: Attempting to delete a student who is not in the system........................84
Figure 3-34: Expected output of Test Case: Attempting to delete a student who is not in the system......84
Figure 3-35: Actual output of Test Case: Attempting to delete a student who is not in the system..........85
Figure 3-36: Input of Test Case: Sorting or displaying students when no students exist............................85
Figure 3-37: Expected output of Test Case: Sorting or displaying students when no students exist..........85
Figure 3-38: Actual output of Test Case: Sorting or displaying students when no students exist..............85
Figure 3-39: Input of Test case: Enter invalid choice..................................................................................86
Figure 3-40: Actual of Test case: Enter invalid choice.................................................................................86
Figure 3-41: Student Class.......................................................................................................................... 88
Figure 3-42: StudentManager class............................................................................................................ 89
Figure 3-43: Adding a Student.................................................................................................................... 90
Figure 3-44: Editing a Student.................................................................................................................... 90
Figure 3-45: Deleting a Student..................................................................................................................91
Figure 3-46: Sorting Students by Score.......................................................................................................91
Figure 4-47: Searching for a Student.......................................................................................................... 92
Figure 3-48: Displaying All Students........................................................................................................... 92
Figure 3-49: Test Run Results Setting the Maximum Number of Students.................................................94
Figure 3-50: Example Runs of Adding Students (part 1).............................................................................95
Figure 3-51: Example Runs of Adding Students (part 2).............................................................................95
8
Figure 3-52: Example Runs of Adding Students (part 3).............................................................................95
Figure 3-53: Example Runs of Adding Students (part 4).............................................................................95
Figure 3-54: Example run of Assigning Marks to a Student (part 1)...........................................................96
Figure 3-55: Example run of Assigning Marks to a Student(part 2)............................................................96
Figure 3-56: Example run of Searching for a Student (part 1)...................................................................96
Figure 3-57: Example run of Searching for a Student (part 2)....................................................................97
Figure 3-58: Example run of Sorting Students by Scores (part 1)...............................................................97
Figure 3-59: Example run of Sorting Students by Scores (part 2)...............................................................97
Figure 3-60: Example run of Sorting Students by Scores (part 3)...............................................................97
Figure 3-61: Example run of Editing a Student’s Name..............................................................................97
Figure 3-62: Example run of Deleting a Student (part 1)............................................................................98
Figure 3-63: Example run of Deleting a Student (part 2)............................................................................98
Figure 3-64: Displaying All Students........................................................................................................... 98
Figure 3-65: Handling an Empty Student List(part 1)..................................................................................98
Figure 3-66: Handling an Empty Student List(part 2)..................................................................................99
Figure 4-1: Example: Finding the Maximum Element in an Array.............................................................102
Figure 4-2: Example: Summing Elements in an Array (O(n)).....................................................................103
Figure 4-3: Example: Nested Loop (O(n²))................................................................................................104
Figure 4-4: Example: Comparing Linear Search vs. Binary Search............................................................111
Figure 4-5: Output of Example: Comparing Linear Search vs. Binary Search............................................111
Figure 4-6: Example: Comparing Iterative vs. Recursive Factorial............................................................113
Figure 4-7: Out put of Example: Comparing Iterative vs. Recursive Factorial...........................................114
Figure 4-8: Graph: Execution Time vs Input Size.......................................................................................115
Figure 4-9: Graph: Space Usage vs Input Size...........................................................................................116
Figure 4-10: Using ArrayList for a To-Do List.............................................................................................120
Figure 4-11: Output of Using ArrayList for a To-Do List............................................................................120
Figure 4-12: Using LinkedList for a To-Do List...........................................................................................121
Figure 4-13: Output of Using LinkedList for a To-Do List..........................................................................121
Figure 4-14: Measuring Execution Time for Both Implementations.........................................................123
Figure 4-15: Output of Measuring Execution Time for Both Implementations.........................................124
9
LIST OF TABLE
Table 1-1: Comparison................................................................................................................................15
Table 1-2: Stack Operations........................................................................................................................20
Table 1-3: Queue Operations..................................................................................................................... 21
Table 1-4: Linked List Operations............................................................................................................... 21
Table 1-5: Advantages and Disadvantages of the Memory Stack...............................................................26
Table 1-6: Comparison of the Two Implementations.................................................................................42
Table 1-7: Comparison of the Two Algorithms...........................................................................................49
Table 3-1: Test results reporting.................................................................................................................87
Table 4-1: Summary of Examples............................................................................................................. 106
Table 4-2: Space Complexity Comparison................................................................................................ 115
Table 4-3: Searching for a student in a list of increasing size...................................................................115
Table 4-4 : Space Usage Comparison........................................................................................................116
Table 4-5: Choosing Between an Array and a Linked List for a To-Do List................................................119
Table 4-6 : Performance Results & Proof..................................................................................................125
Table 4-7 : Final Evaluation.......................................................................................................................125
Table 4-8 : Time Complexity Trade-offs....................................................................................................126
Table 4-9 : Space Complexity Trade-offs.................................................................................................. 126
Table 4-10 : Performance Trade-offs Based on Real Execution Time.......................................................127
Table 4-11 : When to Choose Each Data Structure?.................................................................................127
INTRODUCE
I am currently employed at Softnet Development Ltd, a software company specializing in networking
solutions. Our company has been awarded a significant contract to design and implement a middleware
solution that will interface with various systems as part of a service delivery partnership project. This
10
middleware solution includes a computer provisioning interface that utilizes SOAP, HTTP, JML, and CLI
protocols, while the backend communicates with telecommunications providers through CLI.
In my role as an internal software developer, I have been entrusted with the responsibility to educate my
colleagues on the creation and utilization of Abstract Data Types (ADTs). As part of this initiative, I have
been tasked with preparing a presentation that demonstrates how ADTs can enhance software design,
development, and testing for all collaborating partners. Additionally, I am required to compile an
introductory report that formalizes the specification of ADTs and algorithms for distribution among our
partners.
This assignment aims to explore the significance of ADTs in software development, providing insights
into their specifications and practical applications. Through this exploration, I hope to contribute to a
deeper understanding of how ADTs can improve the efficiency and effectiveness of software solutions in
our industry.
11
Role of ADTs: ADTs play a crucial role in software development and design for several reasons:
Encapsulation: ADTs encapsulate data and operations, allowing users to interact with the data
structure without needing to understand its internal workings. This promotes modularity and
separation of concerns.
Example: A Stack ADT allows users to push and pop elements without needing to know how
the stack is implemented (e.g., using an array or a linked list).
Reusability: By defining a clear interface, ADTs can be reused across different programs and
applications. Developers can implement the same ADT in various ways (e.g., using arrays or linked
lists) without changing the interface.
Example: A Queue ADT can be implemented using an array or a linked list, but the operations
(enqueue, dequeue, front) remain the same for users.
Maintainability: Changes to the implementation of an ADT do not affect the code that uses it, as
long as the interface remains consistent. This makes it easier to maintain and update software.
Example: If a Linked List ADT is optimized for performance, the users of the linked list do not
need to change their code as long as the interface (insert, delete, traverse) remains the same.
Abstraction: ADTs provide a level of abstraction that simplifies complex data management tasks.
Users can work with high-level operations without needing to manage the low-level details.
Example: When using a Dictionary ADT, users can add, remove, or search for key-value pairs
without needing to know how the underlying data structure (like a hash table) is
implemented.
Example: A Graph ADT can be used in various algorithms (like Dijkstra's or A* search) without
needing to change the way the graph is represented, allowing for easy integration into
different systems.
12
Figure 1-2: Stack
Definition: A stack is an abstract data type that follows the Last In, First Out (LIFO) principle, meaning
that the last element added to the stack is the first one to be removed. Stacks are often visualized as a
collection of items stacked on top of each other.
Operations:
Pop: Removes and returns the element from the top of the stack.
Peek (or Top): Returns the element at the top of the stack without removing it.
b) Queue
13
Figure 1-3: Queue
Definition: A queue is an abstract data type that follows the First In, First Out (FIFO) principle, meaning
that the first element added to the queue is the first one to be removed. Queues can be visualized as a
line of people waiting for service.
Operations:
Dequeue: Removes and returns the element from the front of the queue.
Front (or Peek): Returns the element at the front of the queue without removing it.
c) Linked List
14
Figure 1-4: Linked List
Definition: A linked list is a linear data structure where elements, called nodes, are stored in a sequence.
Each node contains two parts: the data and a reference (or link) to the next node in the sequence. Unlike
arrays, linked lists do not require contiguous memory allocation.
Operations:
Insert: Adds a new node to the list at a specified position (beginning, end, or middle).
Traverse: Visits each node in the list to perform an operation (e.g., print values).
Use Cases: Linked lists are commonly used in scenarios such as:
Implementing dynamic arrays or other data structures (like stacks and queues).
Managing memory allocation in systems where the size of data is not known in advance.
Compare:
15
managing playlists.
Memory Allocation Typically implemented Typically implemented Dynamic memory
using arrays or linked using arrays or linked allocation; nodes can
lists; can be dynamic. lists; can be dynamic. be scattered in
memory.
Complexity (Time) Push: O(1), Pop: O(1), Enqueue: O(1), Insert: O(1) (at head),
Peek: O(1) Dequeue: O(1), Front: O(n) (at tail or middle),
O(1) Delete: O(n) (search
first), Search: O(n)
Implementation Can be implemented Can be implemented Implemented using
using arrays or linked using arrays or linked nodes with pointers to
lists. lists the next node.
Output:
16
Figure 1-6: Output of Stack example code
b) Queue
Output:
17
c) Linked List
18
Ouput:
a) Stack Operations
All operations on a stack are performed in constant time (O(1)), making stacks very efficient for scenarios
where quick access to the most recently added element is required, such as in function call management
or undo operations in applications.
b) Queue Operations
19
Enqueue Adds an element to the end of O(1)
the queue.
Dequeue Removes and returns the O(1)
element from the front of the
queue.
Front Returns the element at the front O(1)
of the queue without removing
it.
IsEmpty Checks if the queue is empty. O(1)
Similar to stacks, all operations on a queue are performed in constant time (O(1)). This efficiency is
crucial for applications like task scheduling and print job management, where maintaining the order of
processing is essential.
The time complexity for inserting a node at the head of a linked list is O(1), making it efficient for adding
elements. However, searching for a node or deleting a node requires O(n) time, as it may involve
traversing the entire list. This makes linked lists less efficient for operations that require frequent
searching or deletion compared to stacks and queues.
20
Figure 1-11: Memory stack
Definition: “The memory stack is a data structure that operates on a Last In, First Out (LIFO) principle,
used primarily for managing function calls, local variables, and return addresses in programming. It
allows for the dynamic allocation and deallocation of memory as functions are called and return,
facilitating efficient execution and control flow in programs.”
The memory stack is a crucial data structure that manages function calls and local data using a Last In,
First Out (LIFO) approach.
Key Components:
a) Function Call: When a function is invoked, a new stack frame is created on the top of the stack. This
stack frame contains:
Return Address: The address in the program where control should return after the function
execution is complete.
21
Local Variables: Any variables declared within the function.
b) Stack Frame Creation: The stack frame is pushed onto the stack, which involves allocating space for
the return address, parameters, and local variables. The stack pointer (a special register that tracks the
top of the stack) is updated to reflect the new top of the stack.
c) Function Execution: The function executes using the parameters and local variables stored in its stack
frame. During this time, the function can also call other functions, which will create additional stack
frames on top of the current one.
d) Function Return: Once the function completes its execution, it prepares to return. The return value (if
any) is typically placed in a designated register. The stack frame is then popped off the stack, which
involves:
Updating the stack pointer to remove the stack frame from the stack.
Transferring control back to the return address, allowing the program to continue execution from
where the function was called.
e) Memory Management: The stack automatically manages memory allocation and deallocation. When a
function returns, its stack frame is removed, freeing up the memory for future function calls. This
automatic management helps prevent memory leaks, as the memory used by local variables is reclaimed
as soon as the function exits.
22
Figure 1-12: Recursion
Recursion is a technique where a function calls itself to solve a problem. The memory stack manages
these calls by storing stack frames for each function invocation.
a) Multiple Stack Frames: Each recursive call pushes a new frame onto the stack, preserving its own
parameters and local variables.
b) Base Case & Recursive Case: The base case stops recursion, while the recursive case continues calling
itself.
c) Stack Unwinding: When recursion reaches the base case, stack frames are popped one by one,
returning control to previous calls.
d) Memory Usage: Deep recursion increases stack space usage, potentially causing a stack overflow if the
limit is exceeded.
e) Example: Consider a simple recursive function that calculates the factorial of a number ( n ):
23
Recursive Case: If ( n ) is greater than 0, it calls itself with ( n - 1 ) and multiplies the result by ( n ).
Automatic Memory Management: The stack automatically allocates and deallocates memory
for function calls and local variables. This reduces the risk of memory leaks, as memory is
reclaimed as soon as a function exits.
Fast Access: Accessing data on the stack is generally faster than accessing data on the heap.
This is because the stack operates in a LIFO manner, allowing for quick push and pop
operations.
Function Call Management: The stack efficiently manages function calls, including
parameters, local variables, and return addresses. This organization simplifies the execution
flow of programs.
Recursion Support: The stack is well-suited for handling recursive function calls, as each call
creates a new stack frame that maintains its own context, allowing for clear and structured
recursion.
Disadvantages
Limited Size: The stack has a fixed size, which can lead to stack overflow if too many function
calls are made (e.g., deep recursion). This can cause the program to crash.
No Dynamic Memory Allocation: The stack is not suitable for dynamic memory allocation of
large data structures, as it is limited in size and cannot grow like the heap.
Scope Limitations: Variables stored on the stack are only accessible within the function that
created them. This can limit their usability if they need to be accessed outside of that
function.
24
Debugging Complexity: In cases of deep recursion or stack overflow, debugging can become
complex, as it may be difficult to trace back through multiple stack frames to identify the
source of the problem.
Performance Overhead: While stack access is generally fast, excessive function calls
(especially in recursive functions) can lead to performance overhead due to the repeated
creation and destruction of stack frames.
Example: Factorial of 3
Let's visualize the stack frames created during the execution of factorial(3).
25
factorial(3)
factorial(2)
factorial(1)
factorial(0)
Stack Visualization
o Contains the return address and the parameter ( n = 0 ). This is where the base case is
reached, returning 1.
26
factorial(0) returns 1 to factorial(1).
After all function calls have returned, the stack is empty, and the final result of factorial(3) is 6.
This visual representation illustrates how the memory stack manages function calls and local variables
during recursion. Each function call creates a new stack frame, and the stack unwinds as each function
completes, returning control to the previous call. This structure is essential for maintaining the context of
each function call in recursive programming.
1.3 Illustrate, with an example, a concrete data structure for a First in First
out (FIFO) queue. (M1)
A FIFO queue is a linear data structure where the first element added is the first to be removed, similar
to a real-world queue.
Key Characteristics:
27
Dynamic Size: Can grow or shrink as needed.
Limited Access: Elements are added at the rear and removed from the front.
a) Enqueue: This operation adds an element to the back (rear) of the queue. If the queue is full (in the
case of a fixed-size implementation), this operation may fail or require resizing.
Example: If we have a queue containing the elements [A, B], and we enqueue C, the queue will
then contain [A, B, C].
b) Dequeue: This operation removes and returns the element from the front of the queue. If the queue is
empty, this operation may fail or return a special value (like null or None).
Example: If we have a queue containing [A, B, C], and we dequeue, the element A will be
removed, and the queue will then contain [B, C].
c) Peek (or Front): This operation returns the element at the front of the queue without removing it. This
allows users to see which element will be dequeued next.
Example: If the queue contains [B, C], the peek operation will return B, but the queue will remain
unchanged.
d) IsEmpty: This operation checks whether the queue is empty. It returns a boolean value indicating the
status of the queue.
Example: If the queue is empty, the operation will return true; otherwise, it will return false.
e) Size: This operation returns the number of elements currently in the queue.
Example: If the queue contains [B, C], the size operation will return 2.
A FIFO (First-In, First-Out) queue can be implemented using a fixed-size array. In this implementation, we
will maintain two pointers (or indices): one for the front of the queue and one for the rear. The array will
hold the elements of the queue, and we will manage the addition and removal of elements using these
pointers.
28
Key Components of the Array-Based Queue Implementation:
Front Pointer: An index that points to the front of the queue (the element to be dequeued).
Rear Pointer: An index that points to the next available position at the back of the queue (where
the next element will be enqueued).
Size: An integer to keep track of the current number of elements in the queue.
Basic Operations:
a) Enqueue Operation:
Check if the queue is full (i.e., if the size equals the capacity of the array).
If not full, add the new element at the position indicated by the rear pointer and increment the
rear pointer (wrapping around if necessary).
b) Dequeue Operation:
If not empty, retrieve the element at the front pointer, increment the front pointer (wrapping
around if necessary), and decrement the size.
c) Peek Operation:
Return the element at the front pointer without removing it. This allows users to see which
element will be dequeued next.
d) IsEmpty Operation:
e) Size Operation:
29
Figure 1-17: Example Implementation Queue using array (part 1)
30
Figure 1-18: Example Implementation Queue using array (part 2)
Output:
31
Explanation of the Java Implementation:
a) Class Definition: The ArrayQueue class encapsulates the properties and methods of the queue.
b) Attributes:
rear: An index pointing to the next available position at the back of the queue.
c) Constructor: Initializes the queue with a specified capacity and sets the front, rear, and size to zero.
d) Enqueue Method:
Purpose: The enqueue method adds a new element to the back (rear) of the queue.
Implementation Steps:
Check for Full Queue: The method first checks if the queue is full by comparing the current
size with the capacity. If the queue is full, it throws an IllegalStateException.
Add Element: If there is space, the new element is added to the position indicated by the rear
pointer in the array.
Update Rear Pointer: The rear pointer is then incremented. The modulo operation (%
capacity) ensures that if the rear pointer reaches the end of the array, it wraps around to the
beginning of the array.
e) Dequeue Method
Purpose: The dequeue method removes and returns the element from the front of the queue.
Implementation Steps:
Check for Empty Queue: The method checks if the queue is empty by verifying if the size is
zero. If it is empty, it throws an IllegalStateException.
Retrieve Element: The element at the position indicated by the front pointer is retrieved.
32
Clear Slot (Optional): The slot in the array can be cleared (set to null or 0) to indicate that it is
no longer occupied (this is optional).
Update Front Pointer: The front pointer is incremented, and the modulo operation ensures it
wraps around if necessary.
Decrement Size: The size of the queue is decremented by one, and the retrieved element is
returned.
f) Peek Method
Purpose: The peek method allows users to view the element at the front of the queue without
removing it.
Implementation Steps:
Check for Empty Queue: The method checks if the queue is empty. If it is, it throws an
IllegalStateException.
Return Front Element: If the queue is not empty, it returns the element at the front pointer.
g) IsEmpty Method
Implementation Steps:
Return Boolean: It returns true if the size is zero; otherwise, it returns false.
h) GetSize Method
Purpose: The getSize method returns the current number of elements in the queue.
Implementation Steps:
33
Node Class: A nested class that represents each element in the queue. Each node contains the
data and a reference to the next node.
Front Pointer: A reference to the first node in the linked list (the front of the queue).
Rear Pointer: A reference to the last node in the linked list (the rear of the queue).
Size: An integer to keep track of the current number of elements in the queue.
Basic Operations:
a) Enqueue Operation:
If the queue is empty, set both the front and rear pointers to the new node.
If the queue is not empty, link the new node to the current rear node and update the rear pointer
to the new node.
b) Dequeue Operation:
If not empty, retrieve the data from the front node, update the front pointer to the next node,
and decrement the size.
If the queue becomes empty after the dequeue operation, set the rear pointer to null.
c) Peek Operation:
Return the data from the front node without removing it. This allows users to see which element
will be dequeued next.
d) IsEmpty Operation:
e) Size Operation:
34
Figure 1-20: Example Implementation Queue using LinkedList (part 1)
35
Figure 1-21: Example Implementation Queue using LinkedList (part 2)
Output:
36
Figure 1-22: Output of implementation Queue using LinkedList
a) Node Class: A static nested class that represents each element in the queue. It contains an integer
data and a reference to the next node.
b) Attributes:
front: A reference to the first node in the linked list (the front of the queue).
rear: A reference to the last node in the linked list (the rear of the queue).
c) Constructor: Initializes the queue with front and rear set to null and size to zero.
d) Enqueue Method: Creates a new node and links it to the current rear. If the queue is empty, it sets
both front and rear to the new node.
e) Dequeue Method: Retrieves the data from the front node, updates the front pointer, and checks if the
queue becomes empty to set the rear pointer to null.
f) Peek Method: Returns the data from the front node without removing it.
g) IsEmpty Method: Checks if the size is zero to determine if the queue is empty.
1. Memory Usage
Array-Based Queue:
Fixed Size: The size of the queue is determined at the time of creation. If the maximum capacity is
reached, no more elements can be added unless the array is resized, which can be inefficient.
Wasted Space: If the queue is not full, there may be unused space in the array, leading to
inefficient memory usage.
37
Dynamic Size: The queue can grow and shrink dynamically as elements are added or removed.
There is no need to define a maximum size upfront.
No Wasted Space: Memory is allocated for each element as needed, so there is no wasted space.
2. Performance
Array-Based Queue:
Enqueue Operation: O(1) time complexity for adding an element, assuming there is space
available. However, if resizing is needed, it can take O(n) time.
Dequeue Operation: O(1) time complexity for removing an element, but if the queue is
implemented without wrapping (i.e., without circular behavior), it may lead to inefficient use of
space.
Enqueue Operation: O(1) time complexity for adding an element, as it simply involves linking a
new node to the rear.
Dequeue Operation: O(1) time complexity for removing an element, as it involves updating the
front pointer.
3. Ease of Implementation
Array-Based Queue:
Index Management: Care must be taken to manage the front and rear indices correctly, especially
when wrapping around.
Complexity: The implementation is slightly more complex due to the need to manage node
pointers.
Memory Management: Requires careful handling of node creation and deletion, which can
introduce potential memory leaks if not managed properly.
4. Use Cases
Array-Based Queue:
38
Suitable for scenarios where the maximum size of the queue is known in advance and does not
change frequently, such as in fixed-size task scheduling or buffering.
Ideal for scenarios where the number of elements can vary significantly, such as in dynamic task
scheduling, event handling, or when implementing a queue for asynchronous processing.
Both implementations of a FIFO queue have their advantages and disadvantages. The choice between an
array-based queue and a linked list-based queue depends on the specific requirements of the
application, including memory constraints, expected queue size, and performance considerations.
If I need a simple, fixed-size queue and memory usage is not a concern, an array-based
implementation may be sufficient.
If I require a dynamic queue that can grow and shrink as needed, a linked list implementation is
more appropriate.
39
1.4.1 Overview of Sorting Algorithms
Sorting algorithms are essential in computer science for organizing data efficiently. They can be
categorized into:
Bubble Sort: Repeatedly swaps adjacent elements if they are in the wrong order. (O(n²) time
complexity)
Quick Sort: Uses a pivot to divide and conquer. (O(n log n) average case)
Merge Sort: Recursively divides the array and merges sorted halves. (O(n log n))
Selection Sort: Repeatedly selects the smallest/largest element and moves it to the sorted
part. (O(n²))
Insertion Sort: Inserts elements into their correct position in a growing sorted list. (O(n²), but
O(n) for nearly sorted data)
Counting Sort: Counts occurrences of each value and reconstructs the sorted array. (O(n + k),
where k is the range of values)
Radix Sort: Sorts numbers digit by digit. (O(nk), where k is the number of digits)
40
Performance Considerations:
Time Complexity: Measures how execution time grows with input size.
Adaptability: Some algorithms work faster when the data is already partially sorted.
Overview: Insertion Sort is a simple and intuitive sorting algorithm that builds a sorted array (or list) one
element at a time. It is similar to the way you might sort playing cards in your hands. The algorithm
divides the input list into a sorted and an unsorted region, and it iteratively takes one element from the
unsorted region and inserts it into the correct position in the sorted region.
Algorithm Steps:
1. Start with the second element (the first element is considered sorted).
2. Compare the current element (key) with the elements in the sorted region (to its left).
3. Shift all larger elements in the sorted region one position to the right to make space for the key.
4. Insert the key into its correct position in the sorted region.
5. Repeat the process for all elements in the unsorted region until the entire list is sorted.
41
Example: Consider the array [5, 2, 9, 1, 5, 6]. The steps of Insertion Sort would be:
Insert 2: [2, 5, 9, 1, 5, 6]
Insert 1: [1, 2, 5, 9, 5, 6]
Insert 5: [1, 2, 5, 5, 9, 6]
Insert 6: [1, 2, 5, 5, 6, 9]
Time Complexity:
Characteristics:
Stability: Insertion Sort is stable, meaning that equal elements maintain their relative order.
Adaptability: It performs well on partially sorted data, making it efficient for small datasets or
when the input is nearly sorted.
b) Selection Sort
42
Overview: Selection Sort is another simple sorting algorithm that divides the input list into a sorted and
an unsorted region. It repeatedly selects the smallest (or largest, depending on the desired order)
element from the unsorted region and moves it to the end of the sorted region.
Algorithm Steps:
4. Move the boundary between the sorted and unsorted regions one element to the right.
Example: Consider the array [5, 2, 9, 1, 5, 6]. The steps of Selection Sort would be:
Find the minimum (1) and swap it with the first element: [1, 2, 9, 5, 5, 6]
Find the next minimum (2) and swap it with the second element: [1, 2, 9, 5, 5, 6] (no change)
Find the next minimum (5) and swap it with the third element: [1, 2, 5, 9, 5, 6]
Find the next minimum (5) and swap it with the fourth element: [1, 2, 5, 5, 9, 6]
Find the next minimum (6) and swap it with the fifth element: [1, 2, 5, 5, 6, 9]
Time Complexity:
Characteristics:
Stability: Selection Sort is not stable, as it may change the relative order of equal elements during
the selection process.
Adaptability: It does not adapt to the initial order of elements, and its performance remains
consistent regardless of the input.
43
1.4.3 Comparison of the Two Algorithms: Insertion Sort and Selection Sort
In this section, I will compare Insertion Sort and Selection Sort based on several criteria, including time
complexity, space complexity, operational characteristics, and performance in different scenarios.
1. Time Complexity
Insertion Sort:
Best Case: (O(n)) - This occurs when the input array is already sorted, as each element only
needs to be compared once.
Average Case: (O(n^2)) - On average, each element will need to be compared to half of the
already sorted elements.
Worst Case: (O(n^2)) - This occurs when the input array is sorted in reverse order, requiring
the maximum number of comparisons and shifts.
Selection Sort:
Best Case: (O(n^2)) - Regardless of the initial order of elements, Selection Sort always
performs the same number of comparisons.
Average Case: (O(n^2)) - The number of comparisons remains constant, as it always scans the
entire unsorted portion of the array.
Worst Case: (O(n^2)) - Similar to the average case, the number of comparisons does not
change based on the input.
2. Space Complexity
Insertion Sort:
Space Complexity: (O(1)) - Insertion Sort is an in-place sorting algorithm, meaning it requires a
constant amount of additional memory space regardless of the input size.
Selection Sort:
Space Complexity: (O(1)) - Like Insertion Sort, Selection Sort is also an in-place algorithm,
requiring only a constant amount of additional memory.
3. Operational Characteristics
Insertion Sort:
44
Stability: Insertion Sort is a stable sorting algorithm, meaning that it maintains the relative
order of equal elements.
Adaptability: It performs well on partially sorted data, making it efficient for small datasets or
when the input is nearly sorted.
Best Use Case: Ideal for small datasets or when the data is mostly sorted.
Selection Sort:
Stability: Selection Sort is not stable, as it may change the relative order of equal elements
during the selection process.
Adaptability: It does not adapt to the initial order of elements, and its performance remains
consistent regardless of the input.
Best Use Case: Suitable for small datasets where memory usage is a concern, but generally
less efficient than Insertion Sort.
Insertion Sort:
Performs exceptionally well on small or nearly sorted datasets, often outperforming more
complex algorithms like Quick Sort or Merge Sort in these cases.
The best-case scenario (already sorted) allows it to run in linear time, making it very efficient
for small lists.
Selection Sort:
While it has a consistent performance, it is generally slower than Insertion Sort for most
practical scenarios due to its (O(n^2)) time complexity.
It is not suitable for large datasets, as it does not take advantage of any existing order in the
data.
45
Adaptability Adaptive to sorted data Not Adaptive
Best Use Case Small or nearly sorted datasets Small datasets
Sorted Data
Java Implementation
Here’s the Java code that implements both sorting algorithms and measures their execution time:
46
Figure 1-26: Code to measure time of 2 algorithm (part 1)
47
Figure 1-27: Code to measure time of 2 algorithm ( part 2)
Output:
48
Figure 1-28: Measure time of 2 algorithm output
Results Analysis:
a) Performance Comparison:
Insertion Sort is adaptive, making it efficient for sorted or nearly sorted data.
Selection Sort maintains a consistent performance regardless of input order but is slower than
Insertion Sort for most cases.
b) Execution Time:
Insertion Sort runs faster on sorted data and degrades on reverse-ordered data due to its
stepwise insertion mechanism.
Selection Sort maintains a stable but slower execution time, as it always makes a fixed number of
comparisons and swaps.
c) Scalability:
Insertion Sort scales better, especially for smaller or partially sorted datasets.
Selection Sort remains inefficient for larger datasets due to its O(n²) complexity.
d) Conclusion:
Selection Sort is rarely the best choice due to its consistent but suboptimal performance.
49
Understanding these differences helps in choosing the right sorting algorithm based on dataset
characteristics.
2.1 Specify the abstract data type for a software stack using an imperative
definition.(P3)
Imperative Definition: Specifies step-by-step execution, modifying the program’s state (e.g., using
arrays or linked lists).
Key Point: ADT focuses on what operations do, imperative definition focuses on how they are executed.
Stack Operations
a) Push(element):
State Change: Increases the size of the stack and updates the top pointer to the new element.
b) Pop():
50
Description: Removes and returns the element from the top of the stack.
State Change: Decreases the size of the stack and updates the top pointer to the next element. If
the stack is empty, it may raise an error or return a special value.
c) Peek():
Description: Returns the element at the top of the stack without removing it.
d) IsEmpty():
e) Size():
2. To push an element:
3. To pop an element:
a. If the stack is not empty, remove and return the top element.
Java Implementation:
52
Output:
a) Attributes:
top: an integer to track the index of the top element in the stack.
b) Constructor:
Initializes the stack with a given size, sets the capacity, allocates memory for stackArray, and sets
top to -1 (indicating an empty stack).
c) Methods:
push(int x): Adds an element to the top of the stack. Checks for stack overflow before adding.
pop(): Removes and returns the top element from the stack. Checks for stack underflow before
removing.
peek(): Returns the top element without removing it. Checks if the stack is empty.
d) Main Method:
53
Demonstrates the use of the stack by pushing elements, checking the top element, and popping
elements.
Performance Optimization – Enables choosing efficient data structures (array, linked list).
Backtracking Algorithms – Tracks paths in algorithms like maze solving and Sudoku.
Undo Mechanisms – Supports undo operations in text editors and design software.
54
Figure 2-3: Encapsulation
“Information Hiding is closely related to encapsulation and refers to the practice of restricting access to
certain details of an object or module. By hiding the internal workings and exposing only the necessary
parts of an object, information hiding reduces complexity and increases the modularity of the code. This
means that changes to the internal implementation of a class can be made without affecting other parts
of the program that rely on the class, as long as the public interface remains unchanged.”
Ease of Maintenance – Internal changes don’t affect external usage as long as the interface
remains stable.
56
57
Figure 2-5: Illustrative Example
Output:
a)Encapsulation:
The balance attribute is declared as private, meaning it cannot be accessed directly from outside
the BankAccount class. This encapsulation protects the balance from unauthorized access and
modification.
b) Information Hiding:
The internal representation of the balance is hidden from users of the class. Users can only
interact with the balance through the public methods deposit, withdraw, and getBalance. This
means that users do not need to know how the balance is stored or managed internally.
c) Data Integrity:
The methods deposit and withdraw include checks to ensure that only valid operations can be
performed on the balance. For example, a deposit must be positive, and a withdrawal must not
exceed the current balance. This helps maintain the integrity of the data.
Encapsulation: The BankAccount class encapsulates the balance and related operations,
preventing direct access to the balance from outside the class.
Information Hiding: The internal representation of the balance is hidden from users, who interact
with the account only through the provided methods.
58
This Java example effectively demonstrates how encapsulation and information hiding can be applied in
the design of an Abstract Data Type (ADT) to create a robust and user-friendly interface.
In a poorly designed version of the BankAccount class, the balance might be a public attribute, allowing
direct access and modification from outside the class:
1. Direct Access: The ‘balance’ attribute is public, allowing any external code to modify it directly. For
example:
59
Figure 2-8: Direct Access
2. Lack of Control: There are no checks or validations when modifying the balance. This can lead to
invalid states (e.g., negative balances) and data corruption.
3. Increased Complexity: Users of the class must understand the implications of directly manipulating the
balance, increasing the cognitive load and potential for errors.
4. Difficult Maintenance: If the internal representation of the balance needs to change (e.g., changing
from a ‘double’ to a ‘BigDecimal’ for precision), all code that accesses the balance directly would need to
be updated.
With the encapsulated version of the ‘BankAccount’ class, as shown previously, the design addresses
these issues:
60
Figure 2-9: After Applying Encapsulation and Information Hiding
a) Controlled Access: The balance attribute is private, preventing direct access. Users must use the
provided methods to interact with the balance, ensuring that all operations are valid.
b) Data Integrity: The methods include validation checks, ensuring that the balance cannot be set to an
invalid state (e.g., negative balance).
c) Reduced Complexity: Users of the class do not need to understand the internal workings of the
balance. They can simply call deposit, withdraw, and getBalance without worrying about the underlying
implementation.
61
d) Easier Maintenance: If the internal representation of the balance changes, only the BankAccount class
needs to be updated. External code that uses the class remains unaffected as long as the public interface
stays the same.
The comparison highlights the significant advantages of applying encapsulation and information hiding in
the design of Abstract Data Types (ADTs). By protecting the internal state of an object and providing a
controlled interface for interaction, these principles enhance data integrity, reduce complexity, and
facilitate easier maintenance, ultimately leading to more robust and reliable software systems.
Problem Statement
Soft Development ABK, a software workshop for small and medium enterprises, has requested a Student
Management System that allows users to:
[0 – 5.0) → Fail
3. Perform operations like Add, Edit, Delete, Sort, and Search for students.
Since ranking, searching, and sorting students efficiently is essential, we need a complex ADT (Abstract
Data Type) and an efficient algorithm to handle student records properly.
62
3.1.2 ADT and Algorithm Used
To effectively manage student records and their rankings, I have chosen ArrayList as the primary Abstract
Data Type (ADT). The ArrayList in Java provides dynamic storage for student objects, allowing me to add,
edit, delete, search, and sort students efficiently.
The student list is implemented using an ArrayList<Student>, where each student is an object of the
Student class. This ADT allows for:
Algorithm Selection
For sorting and searching, I need efficient algorithms since the number of students may vary.
I will use Collections.sort() with a custom Comparator to sort students by their average marks in
descending order.
63
Java’s built-in sorting method (Collections.sort()) uses Timsort, a hybrid sorting algorithm that
combines Merge Sort and Insertion Sort for optimal performance.
Since student IDs are not necessarily sorted, a simple linear search will be used to find a student
by ID.
For larger datasets, I could use a HashMap for O(1) lookup time, but since the dataset is expected
to be small, linear search suffices.
3. Ranking Algorithm
I will compute the average score for each student and determine their ranking based on the
predefined score ranges.
64
3.1.3 Program Implementation
My Student Management System follows the Object-Oriented Programming (OOP) paradigm in Java.
OOP is a programming paradigm based on the concept of objects, which are instances of classes that
contain data (fields/attributes) and behaviors (methods).
a) Encapsulation
Each student's data (ID, name, marks) is stored inside a Student class.
The fields are private, meaning data can only be accessed or modified through getter and setter
methods.
Encapsulation prevents direct modification of data, ensuring better security and control.
b) Abstraction
65
Figure 3-6: Abstraction of program
The complex logic of ranking and calculating averages is hidden inside the Student class.
Users of the class do not need to know how ranking is calculated; they just call getRanking().
Abstraction hides complexity and makes the Student class easy to use.
a) My Main.java class acts as a controller that interacts with the Student objects stored inside an
ArrayList<Student>.
In OOP design, the controller handles logic and interacts with objects. In my project, Main.java is the
controller—it manages Student objects by calling their methods.
66
Figure 3-7: Main.java class
In my project, I use an ArrayList<Student> to store student records dynamically, meaning it can grow or
shrink as needed.
67
Each Student object has attributes (ID, name, marks) and methods (calculateAverage, getRanking,
setMarks, etc.).
Each student has a unique ID, name, and marks stored in attributes.
The student object itself can calculate its own average and ranking (encapsulation).
The controller (Main.java) doesn’t need to know the formula—it just calls
student.calculateAverage().
The menu in Main.java allows users to add, edit, delete, sort, search, and display students interactively.
68
69
Figure 3-10: Menu-driven approach to interact with the user
Based on the input, the corresponding method in Main.java is called (e.g., addStudent(),
deleteStudent()).
1. Strengths of My Implementation
o Scalability: I can easily extend the program (e.g., add new student types).
o Sorting: Uses Java’s Timsort (O(n log n)), which is efficient for moderate-sized data.
o Searching: Uses Linear Search (O(n)), which works well for small datasets.
o Users can easily add, edit, delete, sort, and search students through a simple text-based
menu.
o The do-while loop ensures the user can keep interacting until they choose to exit.
o The size of the list is not fixed, so the program can handle any number of students within
the user-defined limit.
2. Limitations of My Implementation
70
Linear Search Can Be Slow for Large Datasets
o Searching for a student by ID is done using a Linear Search (O(n)), which is not optimal for
large datasets.
o Improvement: I could use a HashMap (O(1)) to store students with their ID as the key.
o Improvement: I could maintain a sorted list and use Binary Insertion instead of re-sorting
every time.
o Currently, student data is lost when the program exits because everything is stored in
memory.
o Improvement: I could implement file handling (CSV, JSON, or database storage) to save
and load student records.
First, I tested adding students to ensure that their details were stored correctly. The program correctly
prevented duplicate Student IDs, maintaining data integrity. I also tested the sorting function by entering
students with varying scores and verifying that they were arranged in the expected order.
Next, I performed searches using both valid and invalid Student IDs. The program successfully retrieved
students when given valid IDs and displayed appropriate error messages when searching for non-existent
students. Another important test involved handling incorrect numerical input. When entering marks, I
deliberately provided non-numeric values to check if the program handled errors gracefully, which it did
by displaying an appropriate message and prompting for re-entry.
Additionally, I tested the responsiveness of the system by performing multiple operations in sequence,
such as adding, editing, deleting, and searching students within a single session. The program maintained
stability and did not crash, even when handling edge cases such as searching for a student after deleting
them.
71
Through this testing process, I confirmed that the program functions as intended. However, I identified
areas for improvement, such as implementing persistent storage to retain student records after the
program exits and optimizing search operations for better efficiency. Despite these potential
enhancements, the program meets the required specifications and successfully demonstrates the use of
a complex ADT and algorithm in solving a real-world problem.
When entering student marks, users might accidentally input non-numeric values (e.g., letters or
symbols). To prevent this, I use a while loop with exception handling to repeatedly prompt the user until
they enter a valid number.
If the user enters a non-numeric value, the program catches the NumberFormatException and
asks for valid input.
If the number is outside the valid range (0-10), it displays an error and asks again.
72
To maintain data integrity, the program checks if a Student ID already exists before adding a new
student.
If a duplicate ID is found, the program displays an error message and does not add the student.
User input:
Test result:
When searching for or deleting a student, the program handles cases where the Student ID does not
exist.
73
Figure 3-15: Handling Student Not Found Errors code (part 1)
If a user searches for a non-existent Student ID, it displays an error instead of returning null.
When attempting to delete a non-existent student, the program prevents accidental errors by
showing a message.
Certain operations, like sorting and displaying students, should not run if there are no students in the list.
74
Figure 3-17: Handling Empty Student List Errors code
If there are no students, sorting and displaying functions show an error instead of running on an
empty list.
If a user enters an invalid option in the menu, the program displays an error instead of crashing.
75
After implementing error handling, I conducted extensive testing to ensure the program works correctly
under different conditions. The test cases below cover normal operations, error handling, and edge
cases. Each test case includes the input, expected output, and actual output to verify correctness.
Input:
Expected Output:
Figure 3-20: Expected Output of test Case: Entering non-numeric values for marks
Actual output:
Figure 2-21: Actual output of test Case: Entering non-numeric values for marks
Test passed
Input:
76
Expected output:
Figure 3-23: Expected output of test Case: Adding a student with an existing ID
Actual output:
Test passed
Input:
Expected output:
Actual output:
Figure 3-27: Actual output of Test Case 1: Searching for an existing student
Test passed
Input:
77
Figure 3-28: Input of Test Case 2: Searching for a non-existent student
Expected output:
Figure 3-29: Expected output of Test Case 2: Searching for a non-existent student
Actual output:
Figure 3-30: Actual output of Test Case 2: Searching for a non-existent student
Test passed
Before Sorting:
Figure 3-31: Test Case 1: Sorting students based on their average marks (Before Sorting)
Figure 3-32: Test Case 1: Sorting students based on their average marks (After Sorting)
Test passed
78
Input:
Figure 3-33: Input of Test Case: Attempting to delete a student who is not in the system
Expected output:
Figure 3-34: Expected output of Test Case: Attempting to delete a student who is not in the system
Actual output:
Figure 3-35: Actual output of Test Case: Attempting to delete a student who is not in the system
Test passed
Input:
Figure 3-36: Input of Test Case: Sorting or displaying students when no students exist
Expected output:
79
Figure 3-37: Expected output of Test Case: Sorting or displaying students when no students exist
Actual output:
Figure 3-38: Actual output of Test Case: Sorting or displaying students when no students exist
Test passed
Input:
Actual output:
Test passed
80
Search invalid ID Error message Matches ✅ Pass
Sorting students Sorted correctly Matches ✅ Pass
Deleting non-existent Error message Matches ✅ Pass
student
Sorting an empty list Error message Matches ✅ Pass
Enter invalid menu Error message and re- Matches ✅ Pass
choices prompt
In an educational environment, teachers need an efficient way to store, update, and rank students based
on their grades. Manually managing student records and computing rankings can be time-consuming and
error-prone.
To solve this, I developed a Student Management System that allows users to:
81
To solve the problem of managing a student list and ranking students based on their grades, I structured
my program using Object-Oriented Programming (OOP) principles and implemented Abstract Data Types
(ADT) and algorithms effectively. Below is a detailed breakdown of each component.
The Student class represents a single student and encapsulates their attributes and behaviors.
Encapsulates data: Provides getter and setter methods to safely access and modify student
details.
82
Figure 3-41: Student Class
Benefits:
83
Encapsulation: Prevents direct modification of private fields.
Data Formatting: The toString() method provides a clean display of student details.
The StudentManager class (inside Main.java) acts as a controller to manage the list of students.
Features of StudentManager
Implements methods for adding, editing, deleting, sorting, and searching students.
Benefits:
3. Adding a Student
Before adding a student, the program ensures that the Student ID is unique and that marks are valid.
Steps
84
Figure 3-43: Adding a Student
Bebefits:
Ensures that no more students are added beyond the allowed limit.
4. Editing a Student
Allows modification of a student’s name and marks while preserving the ID.
Benefits:
85
Ensures student identity is preserved while allowing name changes.
5. Deleting a Student
Benefits:
Uses Collections.sort() with a custom comparator to sort students in descending order of average marks.
Benefits:
86
Figure 4-47: Searching for a Student
Benefits:
Benefits:
87
o The program follows OOP principles by organizing logic into classes (Student, Main).
o Encapsulation ensures that student data is protected and only accessible through getter
and setter methods.
o The Main class acts as a controller, managing student operations without modifying
student details directly.
o Dynamic storage allows adding and removing students without memory constraints.
o Sorting is handled using Collections.sort(), which internally uses Timsort (O(n log n)),
making it fast and reliable.
Issue: Searching for a student by ID requires scanning the entire list (O(n)), which is inefficient
for large datasets.
88
Potential Improvement: Use HashMap<String, Student> for O(1) lookup time instead of
ArrayList.
Potential Improvement: Instead of sorting every time, maintain a sorted list and use binary
insertion (O(log n)) to add students in sorted order.
Issue: Right now, student records are stored in memory (ArrayList), but they are lost when the
program is closed.
Potential Improvement: Implement file storage (CSV, JSON, or database) to save and load
student records automatically.
Issue: Right now, the program runs in text-based mode (console), which can be improved with
a Graphical User Interface (GUI) for better user experience.
Potential Improvement: Use Java Swing or JavaFX to create a GUI-based student management
system.
When I first launched the program, it prompted me to enter the maximum number of students allowed
in the class. This ensures that the system does not exceed the specified limit.
Example Run:
Figure 3-49: Test Run Results Setting the Maximum Number of Students
89
After entering 3, the program restricted the total number of students to this limit.
The program correctly sets a student limit, preventing more students from being added than allowed.
Example Runs:
However, when I tried adding a fourth student, the program prevented the action since the maximum
limit was reached:
90
The program correctly enforces the student limit and prevents exceeding the allowed number of
students.
But when I entered an invalid value (letters instead of numbers), the program handled the error
gracefully:
The program correctly validates input, ensuring that only numbers between 0 and 10 are accepted.
When I searched for a valid student, the program displayed their details:
91
Figure 3-57: Example run of Searching for a Student (part 2)
The search function works correctly, allowing users to find students while handling invalid IDs properly.
I added more marks to students and tested the sorting feature, which arranged them in descending
order of average marks:
Before Sorting:
After Sorting:
The sorting algorithm correctly ranks students from highest to lowest score.
When I wanted to correct a student’s name, I used the edit function. The program preserved the Student
ID while updating the name:
The program allows easy modification of student details while maintaining ID consistency.
7. Deleting a Student
92
When I removed a student, the program confirmed the deletion:
The program properly deletes students and prevents errors when trying to remove non-existent entries.
When I displayed all students, the program formatted and printed the details clearly:
Finally, I tested what happens if I try sorting or displaying students when no records exist. The program
correctly displayed an error message:
93
Figure 3-66: Handling an Empty Student List(part 2)
The program prevents unnecessary operations when no students are in the system.
My Student Management System provides an efficient and structured way to manage student records. It
automates sorting, ranking, searching, and validation, making it an effective solution for educational
institutions.
With future enhancements like database storage and a graphical interface, this system can be expanded
into a full-featured student management application.
4.1 Discuss how asymptotic analysis can be used to assess the effectiveness
of an algorithm. (P6)
Asymptotic analysis is a method of describing the limiting behavior of a function when the argument
tends towards a particular value or infinity. In computer science, it is commonly used to analyze the
performance of algorithms as the input size grows.
Importance:
94
Identifies bottlenecks to optimize speed and memory.
Real-Life Example:
Algorithm A: Fast for small inputs (100 numbers in 1 sec) but slow for large ones (1M numbers in
1 hour).
Algorithm B: Slower initially (100 numbers in 2 sec) but scales efficiently (1M numbers in 10 sec).
Big O (O): Upper bound, worst-case complexity (e.g., O(n²) means quadratic growth).
Big Omega (Ω): Lower bound, best-case complexity (e.g., Ω(n) means linear growth).
Big Theta (Θ): Tight bound, exact asymptotic behavior (e.g., Θ(n log n) means proportional
growth).
Little o (o): Loose upper bound, grows slower than a function (e.g., o(n²) is slower than n²).
Little omega (ω): Loose lower bound, grows faster than a function (e.g., ω(n) is faster than n).
Identify Basic Operations: Find key operations affecting runtime (e.g., comparisons, assignments).
Define Input Size (n): Represent input size, such as list length or matrix size.
95
Figure 4-1: Example: Finding the Maximum Element in an Array
Asymptotic Analysis
Assignment (max = arr[i]) → O(1) (runs n-1 times, in the worst case)
The input size n represents the number of elements in the array (arr.length).
T(n)=1+(n−1)+(n−1)+(n−1)+1T(n) = 1 + (n - 1) + (n - 1) + (n - 1) + 1T(n)=1+(n−1)+(n−1)+(n−1)+1
T(n)=3n−2T(n) = 3n - 2T(n)=3n−2
96
As n grows large, constant terms (-2) become insignificant.
Since execution time grows linearly with input size, the algorithm runs in O(n) (linear time).
Asymptotic Analysis
Addition (total += num) → Happens inside the loop, so it runs n times (O(n)).
97
Return statement → O(1).
T(n)=1+n+n+1=2n+2T(n) = 1 + n + n + 1 = 2n + 2T(n)=1+n+n+1=2n+2
Since the execution time grows linearly with input size, the algorithm runs in O(n) (linear time).
Now, let's analyze a program that prints all pairs of elements in an array.
Asymptotic Analysis
98
1. Identify Basic Operations
The highest order term is n², so lower-order terms and constants are ignored.
Since the execution time grows quadratically as input size increases, the time complexity is O(n²)
(quadratic time).
Summary of Examples
Asymptotic analysis allows us to compare algorithms without running them on specific hardware.
For example, if one sorting algorithm runs in O(n²) and another in O(n log n), we know the latter
is more efficient for large inputs.
Example:
99
Merge Sort → O(n log n) (Scales better).
Without asymptotic analysis, we might choose an inefficient algorithm just because it runs fast for small
inputs.
Some algorithms work well for small datasets but become too slow for large data.
Example:
If the system grows to thousands of students, switching from O(n) to O(1) searching is a necessary
optimization.
Instead of measuring exact runtime (which depends on hardware, compiler, etc.), asymptotic
analysis focuses only on the algorithm's growth rate.
Example: Two computers might run the same algorithm at different speeds, but asymptotic
complexity remains the same.
Example: If an algorithm runs in O(n²) and can be optimized to O(n log n), the speed
improvement is significant for large n.
Instead of writing code blindly, developers can use asymptotic analysis to guide optimizations.
Asymptotic analysis helps reduce execution time and memory usage by choosing more efficient
algorithms.
100
Example: If two algorithms achieve the same result, the one with lower complexity will save
computing resources.
Better efficiency means lower costs and better user experience in real-world applications.
In databases and search engines, Binary Search (O(log n)) is preferred over Linear Search (O(n)) for
efficiency.
Merge Sort (O(n log n)) is used instead of Bubble Sort (O(n²)) in large datasets.
Example: In e-commerce platforms, sorting thousands of products quickly improves the user
experience.
Sorting efficiency is crucial for applications like ranking, filtering, and recommendation systems.
Algorithms with O(n) or better complexity are essential in big data applications where billions of
records need to be processed.
Example: Google and Facebook use optimized hashing (O(1)) and graph algorithms (O(n log n)) for
data retrieval.
Gradient Descent (O(n)) is optimized over Brute Force Training (O(n²)) in machine learning.
101
Example: Training an AI model on millions of data points requires selecting the best optimization
algorithms to reduce computation time.
This ensures AI models can be trained efficiently within practical time limits.
1. Introduction
2. Asymptotic Notations
Explain Big O, Omega (Ω), and Theta (Θ) with simple examples.
4. Practical Applications
List real-world use cases, such as search engines, sorting large datasets, or AI training models.
5. Conclusion
102
4.2.1 What is Algorithm Efficiency?
Definition
Algorithm efficiency refers to how well an algorithm performs in terms of execution time and memory
usage as the input size increases. It helps determine which algorithm is best suited for a given problem,
especially when dealing with large datasets.
This example measures the time taken by Linear Search (O(n)) and Binary Search (O(log n)) to find an
element in an array.
103
Figure 4-4: Example: Comparing Linear Search vs. Binary Search
Output:
Figure 4-5: Output of Example: Comparing Linear Search vs. Binary Search
104
4.2.3 Measuring Space Usage
Another important way to evaluate algorithm efficiency is by analyzing its space complexity, which refers
to the amount of memory an algorithm uses during execution.
Prevents programs from using excessive memory, which can cause crashes.
Important for embedded systems and mobile applications where memory is limited.
This example shows how recursion can use more memory than iteration.
105
Figure 4-6: Example: Comparing Iterative vs. Recursive Factorial
Output:
106
Figure 4-7: Out put of Example: Comparing Iterative vs. Recursive Factorial
Recursion uses more memory because each recursive call is stored in the call stack.
Execution Time vs Input Size → Shows how Linear Search (O(n)) and Binary Search (O(log n))
perform as input size increases.
Memory Usage vs Input Size → Compares Iterative Factorial (O(1)) vs Recursive Factorial (O(n)).
107
100000 250 ms 3 ms
1000000 1200 ms 5 ms
Linear Search (O(n)) increases much faster than Binary Search (O(log n)).
Graph Representation:
Binary Search remains fast, while Linear Search slows down significantly.
108
15 16 bytes 240 bytes
20 16 bytes 320 bytes
30 16 bytes 480 bytes
Recursive Factorial (O(n)) uses more memory as n increases, while Iterative Factorial remains constant
(O(1)).
Graph Representation:
Recursive Factorial increases memory usage linearly, while Iterative Factorial remains efficient.
109
A trade-off in Abstract Data Types (ADTs) refers to the need to balance different design factors, such as
time complexity, space efficiency, and ease of use, when choosing a data structure. Since no single ADT
excels in all aspects, developers must make compromises based on the specific requirements of a system
(Goodrich & Tamassia, 2014).
For example, an array-based list offers fast random access (O(1)), but inserting elements in the middle is
slow (O(n)) due to shifting elements. On the other hand, a linked list allows fast insertions and deletions
(O(1) at the head) but suffers from slower access time (O(n))
1. Time Complexity
Each ADT has different time complexities for operations like searching, inserting, and deleting. For
example, an array-based list (ArrayList) allows O(1) access to any element using an index, but inserting or
deleting in the middle requires shifting elements (O(n)). A linked list allows fast insertions and deletions
(O(1) at the front) but takes O(n) to find an element.
2. Space Complexity
Some ADTs require extra memory overhead. Linked lists use extra space for pointers, while arrays can
waste memory if they are pre-allocated with more space than needed. A hash table may use more
memory than a sorted array but offers faster lookups.
Simple ADTs like arrays are easy to implement but lack flexibility. Linked lists, trees, and hash tables
require more code and logic but provide better adaptability. The more complex the structure, the harder
it is to debug and maintain.
110
4. Flexibility and Modifiability
Linked Lists & Trees (Dynamic Structures): Can grow or shrink dynamically but involve additional memory
management.
If fast lookups are needed, an array or hash table is preferred. If frequent insertions and deletions are
required, a linked list or binary tree might be better.
6. Application Requirements
Choosing the right ADT depends on the specific use case. A stack is great for function calls and undo
operations, while a queue is better for scheduling tasks. If searching is common, a binary search tree is a
good choice.
Example 1: Choosing Between an Array and a Linked List for a To-Do List
Imagine I am building a to-do list application where users can add, remove, and access tasks. Should I use
an array (ArrayList) or a linked list (LinkedList)?
Table 4-5: Choosing Between an Array and a Linked List for a To-Do List
Operation Array (ArrayList) Linked List (LinkedList)
Adding a task at the end Fast (O(1) amortized) Fast (O(1))
Adding a task in the middle Slow (O(n) due to shifting) Fast (O(1) if pointer is known)
Removing a task in the middle Slow (O(n)) Fast (O(1) if pointer is known)
Accessing a task by index Fast (O(1)) Slow (O(n))
Trade-off:
If the app mainly retrieves tasks by index, an ArrayList is better because it provides O(1) access.
If the app frequently inserts or deletes tasks in the middle, a LinkedList is better since it avoids
shifting elements.
111
A web browser needs to handle the "Back" and "Forward" buttons. Which ADT is better?
Stack (LIFO - Last In, First Out): Perfect for "Back" functionality, as the most recent page is
revisited first.
Queue (FIFO - First In, First Out): Useful for loading web pages in order, such as processing
multiple download requests.
Trade-off:
Stack is better for undo operations, where the last action must be undone first.
Queue is better for processing tasks in order, like printer job scheduling or background
downloads.
A phonebook application needs to store and retrieve contacts efficiently. Two common data structures
are:
Trade-off:
If quick lookups are needed without maintaining order, a hash table is best.
If sorted data is required, a binary search tree is better, as it allows in-order traversal.
Each ADT has strengths and weaknesses, and the right choice depends on the application's
requirements.
112
Figure 4-10: Using ArrayList for a To-Do List
Output:
Analysis
Fast retrieval of tasks using get(index), making it ideal if I frequently need to display tasks.
113
Figure 4-12: Using LinkedList for a To-Do List
Output:
Analysis
114
Adding elements at the end (O(1) for both).
Adding elements at the beginning (O(n) for ArrayList, O(1) for LinkedList).
Removing elements from the beginning (O(n) for ArrayList, O(1) for LinkedList).
Output:
115
Figure 4-15: Output of Measuring Execution Time for Both Implementations
b) Final Evaluation
c) Conclusion
116
ArrayList is faster for random access (O(1)), making it better for applications where I frequently
retrieve elements by index.
LinkedList is much faster for insertions and deletions (O(1) for adding/removing at the beginning),
making it better for dynamic modifications.
ArrayList struggles with inserting/removing at the start (O(n)), proving that shifting elements is
costly.
LinkedList struggles with accessing elements (O(n)), proving that traversal slows it down.
Analysis:
117
Analysis:
LinkedList has additional memory overhead due to storing pointers for each node.
ArrayList benefits from better cache locality, making it faster for sequential access.
Final Conclusion
This analysis is backed by real execution results, ensuring a data-driven decision for choosing the best
ADT.
118
CONCLUSION
In conclusion, the design and implementation of the Student Management System for Soft Development
ABK has proven to be an effective solution addressing the challenges faced by educational institutions in
managing student records, tracking performance, and facilitating communication. The project
successfully integrates essential functionalities such as student data management, ranking based on
academic performance, and efficient search and retrieval of student information, all while ensuring data
integrity and user-friendly interaction.
The use of an ArrayList as the primary data structure allows for dynamic storage and efficient
management of student records, enhancing the system's operational efficiency. The implementation of
robust error handling mechanisms ensures that the system remains reliable and user-friendly, even in
the face of invalid inputs or unexpected scenarios. Through comprehensive testing and validation, the
system has demonstrated its capability to meet both user and system requirements, providing a
dependable platform for educational institutions to streamline their administrative processes.
This Student Management System empowers educational organizations with the necessary tools to
enhance their day-to-day operations, improve student engagement, and support informed decision-
making. The project's success highlights the significance of a well-structured system in promoting
academic excellence and achieving institutional objectives. As educational environments continue to
evolve, the implementation of such systems will be crucial in adapting to the changing needs of students
and educators alike.
EVALUATION
Upon evaluating the Student Management System developed for Soft Development ABK, it is evident
that the project has effectively addressed the primary challenges faced by educational institutions in
managing student records and performance tracking. The system's comprehensive functionalities,
including student data management, ranking based on academic performance, and efficient search
capabilities, have been seamlessly integrated to enhance operational efficiency. The implementation of
robust error handling and input validation ensures that the system remains reliable and user-friendly,
allowing users to perform their tasks without unnecessary complications.
The system's dynamic data management capabilities empower educators and administrators to make
informed decisions based on accurate and up-to-date information regarding student performance.
However, the initial adaptation phase may pose a challenge for some users who need to familiarize
themselves with the new technology. Despite this, the opportunities for future enhancements—such as
119
implementing a graphical user interface (GUI) or integrating with external educational tools—present
promising avenues for growth.
Overall, the Student Management System stands as a valuable asset for educational institutions, with the
potential to significantly improve administrative processes and support better academic outcomes.
REFERENCE
Bloch, J. (2018) Effective Java. 3rd edn. Boston: Addison-Wesley.
Goodrich, M. T., & Tamassia, R. (2014). Data Structures and Algorithms in Java (6th ed.). Wiley.
Cormen, T.H., Leiserson, C.E., Rivest, R.L. and Stein, C. (2009) Introduction to Algorithms. 3rd ed.
Cambridge, MA: MIT Press.
Arnold, K., Gosling, J. and Holmes, D., 2005. The Java Programming Language. 4th edn. Boston: Addison-
Wesley.
Horstmann, C.S. and Cornell, G. (2013) Core Java Volume I: Fundamentals. 9th edn. Prentice Hall
Sierra, K. and Bates, B., 2005. Head First Java. 2nd edn. Sebastopol, CA: O'Reilly Media.
Liang, Y.D., 2018. Introduction to Java Programming and Data Structures. 11th edn. Hoboken, NJ:
Pearson.
Deitel, P.J. and Deitel, H.M., 2017. Java: How to Program. 11th edn. Boston: Pearson.
Schildt, H., 2018. Java: The Complete Reference. 11th edn. New York: McGraw-Hill Education.
Stallings, W. (2015) Computer Organization and Architecture: Designing for Performance. 9th edn.
Boston: Pearson.
Venkat, S., 2014. Functional Programming in Java. 1st edn. Boston: Pragmatic Bookshelf.
Hunt, A. and Thomas, D., 1999. The Pragmatic Programmer: Your Journey to Mastery. Boston: Addison-
Wesley.
Gamma, E., Helm, R., Johnson, R. and Vlissides, J., 1994. Design Patterns: Elements of Reusable Object-
Oriented Software. Boston: Addison-Wesley.
120
Flanagan, D., 2019. JavaScript: The Definitive Guide. 7th edn. Sebastopol, CA: O'Reilly Media.
Bennett, S., McRobb, S., & Farmer, R. (2010). Object-Oriented Systems Analysis and Design Using UML.
McGraw-Hill Education.
Liskov, B. (1987). Data Abstraction and Hierarchy. In: Proceedings of the 1987 ACM Conference on
Object-Oriented Programming Systems, Languages, and Applications (OOPSLA '87). ACM, New York, NY,
USA, 17-34. DOI: 10.1145/38765.38773.
CSE373: Data Structures and Algorithms Lecture 4: Asymptotic Analysis. (2014). Available at:
https://courses.cs.washington.edu/courses/cse373/14wi/lecture4.pdf (Accessed: 17 March 2025).
Levitin, A. (2007) Introduction to the Design and Analysis of Algorithms. 2nd edn. Boston: Pearson
Addison-Wesley.
121