1. Trang chủ
  2. » Công Nghệ Thông Tin

Phan tuan manh bh00210 data structures algorithms asm1 1st Distinction

94 19 2
Tài liệu đã được kiểm tra trùng lặp

Đang tải... (xem toàn văn)

Tài liệu hạn chế xem trước, để xem đầy đủ mời bạn chọn Tải xuống

THÔNG TIN TÀI LIỆU

Thông tin cơ bản

Tiêu đề Assignment 1 Front Sheet
Tác giả Phan Tuan Manh
Người hướng dẫn Ta Quang Hieu
Trường học BTEC Level 5 HND Diploma in Computing
Chuyên ngành Data Structures and Algorithms
Thể loại Bài tập
Năm xuất bản 2023
Định dạng
Số trang 94
Dung lượng 3,96 MB
File đính kèm DATA STRUCTURES ALGORITHMS_ASM1_1st.zip (3 MB)

Các công cụ chuyển đổi và chỉnh sửa cho tài liệu này

Cấu trúc

  • I. INTRODUCTION (7)
  • II. BODY (9)
    • 1. Data Structures (9)
    • 2. Abstract data type (9)
    • 3. Stack ADT (11)
      • 3.1 Stack operations and working mechanism (12)
      • 3.2 Implementation of a Stack ADT in Code (14)
      • 3.3 Applications/Examples of Stack (17)
    • 1. Memory Stack (18)
    • 2. Operations of a Memory Stack (18)
    • 3. Using the Memory Stack for Function Calls (20)
    • 1. Queue Data Structure (22)
    • 2. FIFO Principle of Queue (22)
    • 3. Implementation of FIFO Queue (23)
    • 1. Sorting Algorithms (26)
      • 1.1 The Purpose of Sorting (26)
      • 1.2 Different Sorting Algorithms (27)
      • 1.3 Comparing Tow Sorting Algorithms (29)
    • 2. Bubble Sort Algorithm (32)
      • 2.1 How does Bubble Sort Work? (32)
      • 2.2 Implementation of Bubble Sort (36)
      • 2.3 Time Complexity (38)
    • 3. Selection Sort Algorithm (39)
      • 3.1 How does Selection Sort Algorithm work? (39)
      • 3.2 Implementation of Bubble Sort (44)
      • 3.3 Time complexity (45)
    • 1. Explanation (7)
    • 2. Dijkstra Algorithm (47)
      • 2.1 How does Dijkstra’s Algorithm works? (47)
      • 2.2 Implementation of Dijkstra’s Algorithm (54)
    • 3. Bellman-Ford Algorithm (57)
      • 3.1 How does Bellman-Ford’s Algorithm works? (58)
      • 3.2 Implementation of Bellman-Ford’s Algorithm (65)
      • 3.3 Time Complexity (70)
    • 1. Introduction to Formal Specification and Types of Languages (71)
    • 2. Definitions of Pre-condition, Post-condition, and Error-condition (71)
      • 2.1 Pre-condition (72)
      • 2.2 Post-condition (72)
      • 2.3 Error-condition (72)
    • 3. Specification of Stack Operations (72)
      • 3.1 Operation: push(item) (72)
      • 3.2 Operation: pop() (73)
      • 3.3 Operation: peek() (74)
      • 3.4 Operation: isEmpty() (75)
      • 3.5 Operation: size() (75)
    • 4. Implementation example Stack in software (76)
    • 1. Understanding Encapsulation and Information Hiding (79)
      • 1.1 Encapsulation (79)
      • 1.2 Information hiding (80)
    • 2. Application of Read-only and Write-only Properties (81)
    • 3. Advantages of Encapsulation and Information Hiding in an ADT (81)
    • 4. Encapsulation Using Accessors and Mutators (81)
    • 5. Encapsulation Using Properties (82)
    • 1. Imperative ADTs and Object Orientation (84)
    • 2. Integrating Imperative ADTs into an Object-Oriented Paradigm (84)
    • 3. Comparing and Contrasting Traditional Imperative ADTs with OOP-based ADTs (85)
    • 4. Discussion on Imperative ADTs as a Basis for Object Orientation (85)
    • 5. Example to prove the viewpoint (87)
      • 5.1 Imperative ADT - Stack Functions (87)
      • 5.2 OOP-based ADT - Stack Class (88)
  • III. EVALUATION (89)
  • IV. CONCLUSION (91)
  • V. REFERENCES (93)

Nội dung

Grade Distinction The primary objectives of this report are as follows: 1. Explanation on how to specify an abstract data type using the example of a software stack: 2. Explanation of the advantages of encapsulation and information hiding when using an ADT: 3. Discussion of imperative ADTs with regard to object orientation:

INTRODUCTION

In the dynamic realm of software development, innovation and efficiency drive success As an in-house developer at Softnet Development Ltd, a leader in network provisioning solutions, I am engaged in a collaborative service provisioning project I have been entrusted with the design and development of a middleware solution that will connect various computer provisioning interfaces, such as SOAP, HTTP, JML, and CLI, to the telecom provisioning network at the back end through CLI.

In this challenging environment, my account manager has assigned me the important task of educating our team on the complexities of designing and implementing abstract data types (ADTs) I am responsible for creating a presentation for our valued partners, highlighting how ADTs can be utilized to improve various facets of software development, including design and testing.

I am preparing an introductory report for all project partners, focusing on the specification of abstract data types (ADTs) and algorithms through formal notation This report will explore the fundamental principles of ADTs and highlight their ability to enhance software engineering practices.

The primary objectives of this report are as follows:

1 Explanation on how to specify an abstract data type using the example of a software stack:

This report will clarify the process of defining and specifying an abstract data type (ADT) using the practical example of a software stack, providing a foundational understanding of broader concepts related to ADTs.

2 Explanation of the advantages of encapsulation and information hiding when using an ADT:

Encapsulation is a fundamental principle of Abstract Data Types (ADTs), emphasizing the importance of hiding implementation details This practice of information hiding enhances software development by promoting modularity, improving code maintainability, and reducing complexity Understanding the significance of encapsulation in ADTs is crucial for creating robust and efficient software systems.

3 Discussion of imperative ADTs with regard to object orientation:

As software development increasingly adopts object-oriented paradigms, it is essential to evaluate the role of imperative Abstract Data Types (ADTs) within this framework This report will provide a comprehensive analysis, highlighting the connection between imperative ADTs and object-oriented programming.

This report aims to establish a solid understanding of abstract data types (ADTs) in software development, highlighting their significance and transformative potential in shaping the future of software engineering.

BODY

Data Structures

A data structure is a technique of organizing the data so that the data can be utilized efficiently There are two ways of viewing the data structure:

Data structures organize information according to specific protocols or rules, which are defined by logical and abstract models These models serve as frameworks for structuring and managing data effectively.

Implementation: The second part is the implementation part The rules must be implemented using some programming language.

Abstract data type

An abstract data type (ADT) is a conceptual framework for a data structure that defines a specific interface without detailing the implementation or the programming language used This abstraction allows developers to focus on the functionality and operations of the data structure while maintaining flexibility in how it is realized.

Abstract data types define data and operations without revealing implementation details They specify the data being stored and the operations applicable, while the underlying implementation varies across programming languages For instance, C uses structures for data representation, whereas C++ employs objects and classes, highlighting the diverse strategies for implementing data structures.

Abstraction in data structures involves simplifying complex systems by focusing on essential features while concealing intricate details For instance, a List is an abstract data type realized through dynamic arrays and linked lists, while a Queue can be implemented using linked lists, arrays, or stacks Additionally, a Map can be represented through various implementations such as tree maps, hash maps, or hash tables.

Users of data types can utilize them without needing to understand their underlying implementation For instance, primitive values such as int, float, and char can be effectively used based solely on the knowledge of their functionalities, without requiring insight into how they are constructed.

A user should understand the functionality of a data type without needing to know its implementation details An Abstract Data Type (ADT) can be likened to a black box that conceals the internal structure and design of the data type, allowing users to focus on its capabilities (GeeksforGeeks, 2023).

An Abstract Data Type (ADT) can be effectively illustrated through the example of a software stack, which highlights its fundamental characteristics.

Stack ADT

To underscore the definition of an Abstract Data Type (ADT), we'll delve into the example of a software stack A stack serves as an exemplary manifestation of the ADT concept

An Abstract Data Type (ADT) is a fundamental programming concept that defines a collection of data and the operations that can be performed on it, without disclosing the specific structure or algorithms used In software development, ADTs often follow the Last-In-First-Out (LIFO) principle, which is crucial for managing data effectively.

LIFO( Last In First Out ):

The Last In, First Out (LIFO) strategy indicates that the most recently added element is the first to be removed A practical example of this can be seen in a stack of plates; the plate placed last is positioned on top, and when the top plate is removed, it demonstrates that the last inserted plate is the first to exit.

The stack ADT exemplifies the fundamental principles of Abstract Data Types (ADTs) by offering a clear interface and specifying allowable operations on the data structure This abstraction effectively hides the complexities of implementation, making it easier for developers to work with data structures without needing to understand their inner workings.

This example of a software stack serves as a concrete manifestation of the ADT concept, highlighting the power of abstraction in software development

3.1 Stack operations and working mechanism

In order to make manipulations in a stack, there are certain operations provided to us

 push() to insert an element into the stack

 pop() to remove an element from the stack

 top() Returns the top element of the stack

 isEmpty() returns true if stack is empty else false

 size() returns the size of stack

3.2 Implementation of a Stack ADT in Code

To demonstrate the stack's nature as an ADT, let’s consider a basic implementation in Java:

Figure 4: Implementation of a Stack ADT (1)

Figure 5: Implementation of a Stack ADT (2)

Figure 6: Implementation of a Stack ADT (3)

This article presents a versatile Stack class implementation capable of handling any data type It features essential methods for stack operations, including pushing, popping, and peeking at the top element, as well as checking if the stack is empty or full and displaying its contents An example usage section illustrates how to effectively utilize this Stack Abstract Data Type (ADT) in Java.

Stacks are fundamental data structures that find application in various real-world scenarios Here are some examples of how stacks are used in practical situations:

Function call management utilizes stacks in programming languages to handle function calls efficiently When a function is invoked, its local variables and parameters are organized within a stack frame As the function completes and returns, the corresponding stack frames are removed from the stack.

Undo functionality in software applications, like text editors and graphic design tools, relies on stacks to manage user actions By pushing each application state onto a stack, users can easily revert to previous states, enhancing their editing experience.

Expression Evaluation: Stacks are utilized in parsing and evaluating mathematical expressions, such as arithmetic expressions or expressions in programming languages They help maintain the correct order of operations

Web browsers utilize stacks to manage users' navigation history, where each visited page is added to the stack The back and forward buttons function by popping pages off the stack, allowing users to easily navigate through their browsing history.

Backtracking algorithms, such as depth-first search (DFS) and recursive algorithms, utilize stacks to monitor the state of the search process, allowing them to revert to previous states when necessary.

In multithreaded applications, each thread maintains its own call stack, which is essential for managing the execution context, including function calls and local variables.

Stacks are utilized to verify balanced parentheses in expressions by pushing open brackets onto the stack and popping them off when encountering close brackets An expression is considered balanced if the stack is empty at the conclusion of the process.

Stacks play a crucial role in task scheduling algorithms by managing tasks that require execution Utilizing a Last-In-First-Out (LIFO) approach, the most recently added task is prioritized for execution first.

P2 Determine the operations of a memory stack and how it is used to implement function calls in a computer

In Part P2, I will delve into the operations of a memory stack and explore how it plays a vital role in the execution of function calls within a computer system.

Memory Stack

A stack is a special area of computer’s memory which stores temporary variables created by a function In stack, variables are declared, stored and initialized during runtime

Temporary storage memory, often referred to as stack memory, automatically erases the memory of variables once the computing task is completed This section primarily holds methods, local variables, and reference variables (Matthew Martin & Martin, 2023).

Operations of a Memory Stack

This operation is used to insert a new data item into the top of the Stack The new item can be inserted as follows:-

In the first step, the Stack Pointer is decremented to point at the address where the data item will be stored

Using the memory write operation, the data item from the Data Register is placed at the top of the stack, specifically at the address indicated by the Stack Pointer (Memory Stack Organization in computer architecture, 2023).

This operation is used to delete a data item from the top of the Stack Data item can be deleted as follows:-

The initial step involves reading the top data item from the Stack into the Data Register, followed by incrementing the Stack Pointer to reference the subsequent data item in the stack Push and Pop operations are facilitated through specific microoperations.

Access to memory with the help of Stack Pointer (SP), and

It totally depends upon the organization of the stack whether the Stack Pointer (SP) is updated by incrementing or decrementing the address values

In computer architecture, the Stack Pointer can either grow by decreasing memory addresses or, alternatively, by increasing them, depending on the design of the stack organization (Memory Stack Organization, 2023).

Peek, or the top operation, enables us to view the value or data at the top of the stack without altering its structure This critical operation is vital for assessing the current state of the stack.

Using the Memory Stack for Function Calls

The memory stack plays a pivotal role in managing function calls in a computer's execution environment Here is a step-by-step explanation of how it is used for this purpose:

When a function is invoked, an activation record, commonly known as a stack frame, is generated and added to the stack This record holds essential details, including the function's parameters, local variables, and the return address.

 Execution: The function begins its execution with access to its own activation record It can access and modify its local variables within this frame

Nested calls occur when a function invokes additional functions, resulting in the creation of new activation records that are added to the stack This process forms a stack of stack frames, facilitating both recursive and nested function calls.

When a function finishes executing or hits a return statement, it removes its activation record from the stack, transferring control back to the function that called it Additionally, the return value can be passed back to the caller.

 Stack Maintenance: The stack is continuously maintained as functions are called and return The top-of-stack pointer is updated accordingly

The memory stack ensures that the execution context of each function is preserved and that functions can return to their respective callers seamlessly

Figure 7: Example Memory Stack for Function Calls

The main function is the entry point of the program

When the main function is called, it pushes its activation record onto the memory stack, including the return address for when it completes execution

The add function is called from within the main function

When the add function is called, its activation record is pushed onto the stack, and it calculates the sum of a and b

After completing its execution, the add function pops its activation record from the stack, and control returns to the main function

The completion of the main function's execution leads to the removal of its activation record from the stack This illustrates the role of the memory stack in managing the execution context of functions, facilitating their invocation and the return of control to the calling functions.

M1 Illustrate, with an example, a concrete data structure for a First In First out (FIFO) queue.

Queue Data Structure

A Queue is defined as a linear data structure that is open at both ends and the operations are performed in First In First Out (FIFO) order

A queue is defined as a data structure where all additions occur at one end and all deletions happen at the opposite end The first element added to the queue is the first one to be removed, following the principle of first-in, first-out (FIFO).

Figure 8: Queue Data Structure (geeksforgeeks, 2023)

FIFO Principle of Queue

A Queue is like a line waiting to purchase tickets, where the first person in line is the first person served (i.e First come first serve)

The front of a queue, also known as the head of the queue, refers to the position of the first entry that will be served In contrast, the last entry in the queue occupies the end position.

23 | P a g e entry in the queue, that is, the one most recently added, is called the rear (or the tail) of the queue See the below figure (geeksforgeeks, 2023)

Figure 9: Fifo Property in Queue (geeksforgeeks, 2023)

Implementation of FIFO Queue

A FIFO (First In, First Out) queue operates like a line at a ticket counter, where the first person in line is the first to be served For instance, consider a queue where we add elements such as 'A', 'B', and 'C' in that order When we remove an element from the queue, 'A' is the first to be dequeued, demonstrating the FIFO principle Additionally, accessing the first element of the queue reveals 'A' as well, confirming that the structure maintains the order of elements effectively This example illustrates the fundamental operations of a FIFO queue: adding, removing, and accessing elements.

Here is an example of how you can implement and illustrate a FIFO queue using Java:

Figure 10: FIFO queue using Java (1)

Figure 11: FIFO queue using Java (2)

This Java example demonstrates the implementation of a FIFOQueue class utilizing a linked list, a popular structure for queue management in Java Key methods such as enqueue, dequeue, isEmpty, size, and peek facilitate interaction with the FIFO queue.

M2 Compare the performance of two sorting algorithms.

Sorting Algorithms

A sorting algorithm rearranges elements in an array or list based on a comparison operator, which determines their new order within the data structure (GeeksforGeeks, 2023).

Sorting in computer science and data processing involves organizing data or elements in a specific order, which is crucial for various applications This process enhances data retrieval efficiency, enables easier data analysis, and improves overall system performance by allowing for quicker access to information.

Sorting enhances data retrieval efficiency by making it easier and faster to locate specific items or conduct searches using algorithms such as binary search This capability is especially vital in databases, where rapid access to data is crucial for optimal performance.

Organized data enhances user experience by presenting information in a clear and accessible format Sorted lists and tables facilitate easier navigation and comprehension, allowing users to quickly locate the information they need.

Many algorithms and data structures, including binary search trees, hash tables, and merge sort, depend on sorted data for optimal efficiency Pre-sorting data can greatly enhance the performance of these algorithms.

Data analysis relies on sorting to uncover patterns, trends, and outliers, making it a crucial preliminary step This process enhances subsequent calculations and visualizations, ultimately improving the overall understanding of the data.

Optimizing input for algorithms is crucial, as many algorithms, particularly divide-and-conquer types like merge sort and quicksort, perform best on sorted or partially sorted data By ensuring data is organized, these algorithms can achieve optimal or near-optimal efficiency.

Sorted data enhances the visual appeal and clarity of information presented to users or stakeholders Logical ordering of data improves the comprehensibility of reports, charts, and graphs, making them easier to interpret.

 Data Aggregation: Sorting is useful when aggregating data For example, when summarizing data for a time period, sorting timestamps or dates allows for easier grouping and calculation

Search Engine Optimization (SEO) involves the use of sorting in web search engines to rank search results by relevance Algorithms employed by search engines utilize sorting techniques to deliver the most pertinent results to users, ensuring that they find what they are looking for efficiently.

 Database Operations: Sorting is integral in database systems for performing various operations like sorting query results, creating indexes, and optimizing query execution plans

 Algorithmic Challenges and Competitions: Sorting algorithms are classic examples of algorithmic challenges They are frequently used as benchmarks for evaluating algorithm performance and as competitive programming problems

Sorting is essential for organizing data, enhancing its accessibility and efficiency for computational and analytical tasks This fundamental operation in computer science and data processing has diverse applications across multiple domains.

There are several sorting algorithms in computer science, each with its own approach and characteristics Here are some of the most commonly used sorting algorithms:

- Simple and easy to implement

- It repeatedly steps through the list, compares adjacent elements, and swaps them if they are in the wrong order

- Inefficient for large lists, as it has a time complexity of O(n^2) in the worst case

- It divides the list into a sorted and an unsorted region, repeatedly selects the smallest element from the unsorted region, and moves it to the sorted region

- It also has a time complexity of O(n^2) in the worst case

- It builds a sorted list one item at a time by repeatedly taking the next unsorted element and inserting it into the correct position within the sorted part of the list

- It is efficient for small lists but has a time complexity of O(n^2) in the worst case

- It uses a divide-and-conquer strategy, dividing the list into smaller sublists until they are sorted and then merging them

- It has a time complexity of O(n log n) in the worst case, making it more efficient than bubble, selection, and insertion sorts for large lists

- It also uses a divide-and-conquer approach, selecting a "pivot" element and partitioning the list into elements smaller and larger than the pivot

- It has an average time complexity of O(n log n) but can degrade to O(n^2) in the worst case

- It builds a binary heap data structure from the list and repeatedly extracts the maximum element to build the sorted list

- It has a time complexity of O(n log n) in the worst case

- It sorts numbers by considering each digit or character in the elements

- It is efficient for sorting integers or fixed-length strings

- It counts the number of occurrences of each element and uses this information to determine the element's position in the sorted list

- It is efficient for small integer ranges but requires extra space

- It distributes elements into buckets based on their values, sorts each bucket individually, and then combines the buckets to form the sorted list

- It is useful for uniformly distributed data

Choosing the right sorting algorithm is crucial and depends on factors such as data size, distribution, and application requirements Each sorting algorithm has unique time and space complexities, making them appropriate for different situations.

In Module M2, I will compare the performance and characteristics of two sorting algorithms: Bubble Sort and Selection Sort

Bubble Sort and Selection Sort were selected for comparison because of their straightforward nature, frequent use in educational settings, minimal space requirements, and significant educational benefits Additionally, they serve as effective benchmarks and are particularly relevant in situations where simplicity is prioritized over performance.

Bubble Sort and Selection Sort are both simple sorting algorithms, but they exhibit different performance characteristics:

Time Complexity: Bubble Sort has a worst-case and average-case time complexity of O(n^2), where

The variable "n" represents the number of elements to be sorted, indicating that as the input size increases, the sorting time escalates quadratically This sorting method relies on pairwise comparisons and swaps adjacent elements, making it particularly inefficient for large datasets.

Bubble Sort is an in-place sorting algorithm that operates with a space complexity of O(1), indicating it is memory-efficient and does not require additional memory proportional to the input size.

Bubble Sort is an inefficient sorting algorithm for large datasets, characterized by its quadratic time complexity, which limits its practical application in sorting extensive lists Instead, it is mainly utilized for educational purposes to help learners grasp fundamental sorting concepts.

Bubble Sort Algorithm

 Traverse from left and compare adjacent elements and the higher one is placed at right side

 In this way, the largest element is moved to the rightmost end at first

 This process is then continued to find the second largest and place it and so on until the data is sorted

2.1 How does Bubble Sort Work?

This flow chart will explain for how does Bubble sort algorithm work

Figure 12: Bubble Sort flow chart

And for more understand the working of bubble sort with the help of the following illustration: Input: arr[] = {6, 3, 0, 5}

The largest element is placed in its correct position, i.e., the end of the array

Figure 13: Bubble Sort Algorithm : Placing the largest element at correct position (geeksforgeeks, 2023)

Place the second largest element at correct position

Figure 14: Bubble Sort Algorithm : Placing the second largest element at correct position (geeksforgeeks, 2023)

Place the remaining two elements at their correct positions

Figure 15: Bubble Sort Algorithm : Placing the remaining elements at their correct positions (geeksforgeeks, 2023)

Below is the implementation of the bubble sort It can be optimized by stopping the algorithm if the inner loop didn’t cause any swap

Figure 16: Implementation of Bubble Sort (1)

Figure 17: Implementation of Bubble Sort (2)

Best Case Sorted array as input Or almost all elements are in proper place [ O(N) ] O(1) swaps Worst Case: Reversely sorted / Very few elements are in proper place [ O(N2) ] O(N2) swaps

Explanation

This report will clarify how to define and specify an abstract data type (ADT) using the practical example of a software stack, providing a foundational understanding of the broader concepts associated with ADTs.

2 Explanation of the advantages of encapsulation and information hiding when using an ADT:

Encapsulation is a fundamental principle of Abstract Data Types (ADTs) that emphasizes the importance of hiding implementation details This practice of information hiding enhances software development by promoting modularity, improving code maintainability, and reducing complexity By focusing on the interface rather than the underlying implementation, developers can create more robust and adaptable software solutions.

3 Discussion of imperative ADTs with regard to object orientation:

As software development increasingly adopts object-oriented paradigms, it is essential to evaluate the role of imperative Abstract Data Types (ADTs) within this framework This report will provide a comprehensive analysis, exploring the connection between imperative ADTs and object-oriented programming.

This report offers a thorough understanding of abstract data types (ADTs) and their significant role in software development It explores the complexities of ADTs and highlights their transformative potential in shaping the future of software engineering.

LO1 Examine abstract data types, concrete data structures and algorithms

P1 Create a design specification for data structures explaining the valid operations that can be carried out on the structures

A data structure is a technique of organizing the data so that the data can be utilized efficiently There are two ways of viewing the data structure:

Data structures organize information according to specific protocols or rules, which are defined by logical and abstract models These models provide the necessary framework for understanding and implementing data organization effectively.

Implementation: The second part is the implementation part The rules must be implemented using some programming language

An abstract data type (ADT) is a conceptual framework for data structures that defines a specific interface without detailing implementation specifics or programming languages This abstraction allows developers to focus on the operations and behaviors of the data type rather than its underlying complexities.

Abstract data types define the data and operations without revealing implementation details They specify what data is stored and the operations that can be performed, but not how these operations are implemented This lack of implementation specifics arises because different programming languages employ distinct strategies; for instance, a data structure in C is implemented using structures, whereas in C++, it is implemented using objects and classes.

In data structures, abstraction is the process of simplifying complex systems by focusing on essential features while concealing unnecessary details For instance, a List can be represented using dynamic arrays or linked lists, while a Queue may be implemented through linked list-based, array-based, or stack-based methods Additionally, a Map can be created using structures like Tree maps, hash maps, or hash tables.

Data type users can utilize primitive values such as int, float, and char without needing to understand their underlying implementation This allows for seamless operations and performance on these data types, emphasizing user convenience over technical complexity.

A user should understand the functionalities of a data type without needing to know its implementation details An Abstract Data Type (ADT) can be likened to a black box that conceals the internal structure and design of the data type (GeeksforGeeks, 2023).

An Abstract Data Type (ADT) can be effectively illustrated through the example of a software stack, which highlights its fundamental characteristics.

To underscore the definition of an Abstract Data Type (ADT), we'll delve into the example of a software stack A stack serves as an exemplary manifestation of the ADT concept

An Abstract Data Type (ADT) is a fundamental programming concept that defines a collection of data and the operations that can be performed on that data, while abstracting away the details of its implementation In software development, an ADT follows the Last-In-First-Out (LIFO) principle, which is crucial for managing data efficiently.

LIFO( Last In First Out ):

The Last In, First Out (LIFO) strategy indicates that the most recently added element is the first to be removed A practical example of this can be seen with a stack of plates; the last plate placed on top is the first one taken off This principle is fundamental in understanding stack data structures and algorithms.

The stack Abstract Data Type (ADT) highlights the fundamental principle of ADTs by offering a well-defined interface and specifying allowable operations on the data structure, effectively abstracting the complexities of its implementation.

This example of a software stack serves as a concrete manifestation of the ADT concept, highlighting the power of abstraction in software development

3.1 Stack operations and working mechanism

In order to make manipulations in a stack, there are certain operations provided to us

 push() to insert an element into the stack

 pop() to remove an element from the stack

 top() Returns the top element of the stack

 isEmpty() returns true if stack is empty else false

 size() returns the size of stack

3.2 Implementation of a Stack ADT in Code

To demonstrate the stack's nature as an ADT, let’s consider a basic implementation in Java:

Figure 4: Implementation of a Stack ADT (1)

Figure 5: Implementation of a Stack ADT (2)

Figure 6: Implementation of a Stack ADT (3)

This article presents a versatile Stack class implementation capable of handling any data type It features essential methods for pushing, popping, and peeking at the top element, along with functionalities to check if the stack is empty or full and to display its contents An example usage section illustrates how to effectively utilize this Stack Abstract Data Type (ADT) in Java.

Stacks are fundamental data structures that find application in various real-world scenarios Here are some examples of how stacks are used in practical situations:

Dijkstra Algorithm

Dijkstra's algorithm, developed by Dutch computer scientist Edsger W Dijkstra in 1956, is widely used for solving single-source shortest path problems in graphs with non-negative edge weights This algorithm efficiently determines the shortest distance between two vertices in a graph.

The algorithm operates by managing two sets of vertices: visited and unvisited It begins at the source vertex, selecting the unvisited vertex with the smallest tentative distance The algorithm then explores the neighbors of this vertex, updating their tentative distances when a shorter path is discovered This iterative process continues until the destination vertex is reached or all accessible vertices have been processed.

2.1 How does Dijkstra’s Algorithm works?

Figure 27: Dijkstra’s Algorithm Flow Chart

Let’s see how Dijkstra’s Algorithm works with an example given below:

Dijkstra’s Algorithm will generate the shortest path from Node 0 to all other Nodes in the graph Consider the below graph:

The algorithm will generate the shortest path from node 0 to all the other nodes in the graph

For this graph, we will assume that the weight of the edges represents the distance between two nodes

As, we can see we have the shortest path from,

Initially we have a set of resources given below :

The Distance from the source node to itself is 0 In this example the source node is 0

Initially, the distance from the source node to all other nodes is set to infinity, indicating that their exact distances are unknown For example, the distances are represented as follows: 0 -> 0, 1 -> ∞, 2 -> ∞, 3 -> ∞, 4 -> ∞, 5 -> ∞, and 6 -> ∞ Additionally, an array of unvisited elements is maintained to track nodes that have not yet been marked or visited.

Algorithm will complete when all the nodes marked as visited and the distance between them added to the path Unvisited Nodes:- 0 1 2 3 4 5 6

Step 1: Start from Node 0 and mark Node as visited as you can check in below image visited Node is marked red

Step 2: Check for adjacent Nodes, Now we have to choices (Either choose Node1 with distance 2 or either choose Node 2 with distance 6 ) and choose Node with minimum distance In this step Node 1 is Minimum distance adjacent Node, so marked it as visited and add up the distance

Step 3: Then Move Forward and check for adjacent Node which is Node 3, so marked it as visited and add up the distance, Now the distance will be:

Step 4: Again we have two choices for adjacent Nodes (Either we can choose Node 4 with distance 10 or either we can choose Node 5 with distance 15) so choose Node with minimum distance In this step Node 4 is Minimum distance adjacent Node, so marked it as visited and add up the distance

Distance: Node 0 -> Node 1 -> Node 3 -> Node 4 = 2 + 5 + 10 = 17

Step 5: Again, Move Forward and check for adjacent Node which is Node 6, so marked it as visited and add up the distance, Now the distance will be:

Distance: Node 0 -> Node 1 -> Node 3 -> Node 4 -> Node 6 = 2 + 5 + 10 + 2 = 19

Below is the implementation of the above approach:

This implementation assumes you have a graph represented as an adjacency matrix and aims to find the shortest path from a source node to all other nodes in the graph

Here's a Java implementation of Dijkstra's algorithm:

Figure 34: Implementation of Dijkstra’s Algorithm (1)

Figure 35: Implementation of Dijkstra’s Algorithm (2)

Figure 36: Implementation of Dijkstra’s Algorithm (3)

Bellman-Ford Algorithm

The Bellman-Ford algorithm is a single-source shortest path algorithm that identifies the shortest paths from a specified source vertex to all other vertices in a graph It is applicable to both weighted and unweighted graphs, making it a versatile tool for various graph-related problems.

The Bellman-Ford algorithm is an effective method for finding the shortest path in a graph, similar to Dijkstra’s algorithm, but it has the added advantage of handling graphs with negative edge weights, making it more versatile despite being slower However, it cannot determine the shortest path if a negative cycle exists in the graph, as repeatedly traversing this cycle will lead to an infinite decrease in path cost, even as the path length increases.

58 | P a g e result, Bellman-Ford is also capable of detecting negative cycles, which is an important feature (geeksforgeeks, 2023)

3.1 How does Bellman-Ford’s Algorithm works?

Let’s see how Bellman-Ford’s Algorithm works with an example given below:

I have a graph which is given below and I want to find whether there exists a negative cycle or not using Bellman-Ford

Step 1: Initialize a distance array Dist[] to store the shortest distance for each vertex from the source vertex Initially distance of source will be 0 and Distance of other vertices will be INFINITY

Figure 38: Initialize a distance array (geeksforgeeks, 2023)

Step 2: Start relaxing the edges, during 1st Relaxation:

Current Distance of B > (Distance of A) + (Weight of A to B) i.e Infinity > 0 + 5

Current Distance of D > (Distance of B) + (Weight of B to D) i.e Infinity > 5 + 2

Current Distance of C > (Distance of B) + (Weight of B to C) i.e Infinity > 5 + 1

Current Distance of F > (Distance of D ) + (Weight of D to F) i.e Infinity > 7 + 2

Current Distance of E > (Distance of C ) + (Weight of C to E) i.e Infinity > 6 + 1

Current Distance of D > (Distance of E) + (Weight of E to D) i.e 7 > 7 + (-1)

Current Distance of E > (Distance of F ) + (Weight of F to E) i.e 7 > 9 + (-3)

Current Distance of F > (Distance of D) + (Weight of D to F) i.e 9 > 6 + 2

Current Distance of D > (Distance of E ) + (Weight of E to D) i.e 6 > 6 + (-1)

Since the graph h 6 vertices, So during the 5th relaxation the shortest distance for all the vertices should have been calculated

Step 7: Now the final relaxation i.e the 6th relaxation should indicate the presence of negative cycle if there is any changes in the distance array of 5th relaxation

During the 6th relaxation, following changes can be seen:

Current Distance of E > (Distance of F) + (Weight of F to E) i.e 6 > 8 + (-3)

Current Distance of F > (Distance of D ) + (Weight of D to F) i.e 8 > 5 + 2

Since, we observer changes in the Distance array Hence ,we can conclude the presence of a negative cycle in the graph

Result: A negative cycle (D->F->E) exists in the graph

3.2 Implementation of Bellman-Ford’s Algorithm

Below is the implementation of the above approach:

The Bellman-Ford algorithm is implemented in Java to determine the shortest path from a single source vertex to all other vertices in a weighted directed graph This algorithm effectively handles graphs with negative weight edges, provided that there are no negative weight cycles.

Figure 45: Implementation of Bellman-Ford’s Algorithm (1)

Figure 46: Implementation of Bellman-Ford’s Algorithm (2)

Figure 47: Implementation of Bellman-Ford’s Algorithm (3)

Figure 48: Implementation of Bellman-Ford’s Algorithm (4)

Best Case Complexity O(E), when distance array after 1st and 2nd relaxation are same , we can simply stop further processing

LO2 Specify abstract data types and algorithms in a formal notation

P3 Using an imperative definition, specify the abstract data type for a software stack

This section presents a formal specification of the Software Stack Abstract Data Type (ADT) through a pseudocode-like notation, detailing the pre-conditions, post-conditions, and error conditions associated with each operation.

Introduction to Formal Specification and Types of Languages

Formal specification utilizes mathematical notations to accurately describe software behavior Various formal specification languages exist, such as axiomatic specifications and the Vienna Development Method (VDM) This section will present formal specification using a pseudocode-like notation.

The Vienna Development Method (VDM), based on Hoare Logic, established a foundation for software design concepts, although numerous informal specification languages are now accessible For instance, the Object Constraint Language (OCL), which complements the Unified Modeling Language (UML), enables the definition of conditions and invariants using formal English.

Formal methods such as VDM and OCL are characterized by their structured approach, yet their association with Design by Contract has led to a certain degree of deformalization This shift may not have been intended by Bertrand Meyer, who originally introduced the metaphor to illustrate the relationship between client code and the service-providing code.

Definitions of Pre-condition, Post-condition, and Error-condition

let's provide clearer definitions for pre-condition, post-condition, and error-condition in the context of software specifications:

Definition: A pre-condition is a condition or set of conditions that must be true or satisfied before an operation or function is executed

Pre-conditions establish the necessary requirements or constraints that ensure the correct functioning of an operation They clearly define the valid state in which an operation can be executed effectively.

Definition: A post-condition is a condition or set of conditions that must be true after an operation or function has been executed

Purpose: Post-conditions describe the expected state of the system, data, or variables after the operation's completion They ensure that the operation has achieved its intended effect

Definition: An error-condition, also known as an exception or error-handling condition, defines what happens when an operation encounters an exceptional situation or error during its execution

Purpose: Error-conditions specify how errors or exceptional cases are handled within an operation They describe the actions or behaviors to be taken when something unexpected occurs

Pre-conditions establish the necessary requirements for an operation, while post-conditions outline the expected results Additionally, error-conditions address any unforeseen issues that may occur during execution Together, these elements are vital for ensuring clear and accurate software specifications, ultimately enhancing the reliability of software systems.

Specification of Stack Operations

Purpose: Adds an item to the top of the stack

Pre-condition: The stack exists

Post-condition: The item item is now at the top of the stack The stack's size has increased by one

Error-condition: If the stack has a defined maximum size and is full, an error should be thrown Java Code:

Purpose: Removes and returns the top item from the stack

Pre-condition: The stack is not empty

When the top item is removed from the stack, the stack's size is reduced by one However, if the stack is empty, an error will be triggered.

Purpose: Returns the top item without removing it

Pre-condition: The stack is not empty

Post-condition: The stack remains unchanged

Error-condition: If the stack is empty, an error should be thrown

Purpose: Determines whether the stack is empty

Pre-condition: The stack exists

Post-condition: Returns true if the stack is empty; otherwise, returns false The stack remains unchanged

Purpose: Returns the number of items in the stack

Pre-condition: The stack exists

Post-condition: The stack remains unchanged and the number of items in the stack is returned Error-condition: None

Implementation example Stack in software

In software development, a stack is commonly utilized to implement undo/redo functionality in text editors, allowing users to easily revert or reapply changes This mechanism tracks user actions, enabling efficient management of edits and maintaining a history of modifications for seamless navigation.

Figure 54: undo/redo functionality in a text editor

We have a TextEditor class that maintains a StringBuilder to store the text content

To manage undo and redo operations effectively, I utilize two stacks: undoStack and redoStack The insertText method facilitates the addition of new text while simultaneously pushing the current state onto the undo stack.

The undo method pops the last state from the undo stack and restores it while pushing the current state onto the redo stack

The redo method pops the next state from the redo stack and restores it while pushing the current state onto the undo stack

We can observe the behavior by inserting text, undoing changes, and redoing changes

M3 Examine the advantages of encapsulation and information hiding when using an ADT

This section explores the benefits of encapsulation and information hiding in Abstract Data Types (ADTs), emphasizing the significance of Read-only and Write-only properties in enhancing data integrity and security.

Understanding Encapsulation and Information Hiding

Firstly, let's clarify the concepts of encapsulation and information hiding within the realm of object- oriented programming:

Encapsulation is the process of combining code and data within a class to simplify complexity and enhance data hiding It is important to note that encapsulation is not solely defined by access specifiers such as private, public, or protected; rather, it allows class members to be categorized as private, public, or protected while still maintaining the integrity of encapsulation.

In object-oriented programming, private members of a class are exclusively accessible to the class's own objects, while public members can be accessed both internally and externally Encapsulation simplifies the user experience by allowing users to focus on what actions to perform with the system, rather than understanding the underlying implementation details.

Encapsulation simplifies complex systems by allowing users to interact with them without needing to understand the underlying mechanisms For example, when a car driver changes gears, they only need to adjust the gear lever, without needing to grasp the intricate workings behind the gear change This approach enhances user experience by reducing complexity and making systems more accessible to end users.

Encapsulation is essential for effectively storing, concealing, and managing data, offering enhanced control over it This technique is particularly valuable for handling secure data or methods, as it limits access to specific information for designated functions or users.

Encapsulation is a fundamental principle of object-oriented programming (OOP) that focuses on the use of objects In OOP, a class serves as a blueprint for creating objects, defining their properties (attributes) and behaviors (methods) By bundling attributes and methods together, a class ensures data protection through encapsulation, allowing for secure and organized code management.

Encapsulation serves to safeguard information from unauthorized modifications and the introduction of new errors By bundling and securing data, it becomes significantly harder for users to unintentionally alter the information This is accomplished by designating the data as private, allowing access and modifications solely through methods within the same class This principle upholds data integrity and minimizes the risk of accidental data corruption.

Information hiding is a fundamental principle in object-oriented programming that safeguards class members from unauthorized access This technique ensures that data members are protected from manipulation or hacking, preserving the integrity of the program Given that data is often the most sensitive and volatile aspect of a program, any unauthorized alterations can lead to incorrect outputs and compromise data integrity.

In Java, information hiding is managed through access modifiers such as private, public, and protected Public data can be accessed from outside the class, so to restrict access and protect your data, it should be declared as private This ensures that private data is only accessible to the objects of the same class.

Information hiding is exemplified by a CheckAccount class containing a private balance attribute, which represents sensitive account information While external applications may be permitted to check the account balance, they are restricted from altering it by declaring the balance attribute as private This approach not only safeguards sensitive data but also simplifies system complexity Encapsulation serves as a key mechanism for achieving information hiding, as it allows for controlled access to the data (Bhakti, 2019).

Application of Read-only and Write-only Properties

Now, let's explore how Read-only (can only be read) and Write-only (can only be written) properties contribute to encapsulation and information hiding:

Read-only properties enable data access while restricting modifications, ensuring a secure method to present information without allowing unauthorized alterations For instance, a "get" property can be implemented to allow users to read the value of a private field while preventing any write operations.

Write-only properties allow for data modification without enabling data reading, making them ideal for scenarios where updating values is necessary without exposing the current state These properties usually feature "set" methods that accept new values for private fields while restricting direct access to the existing data.

Advantages of Encapsulation and Information Hiding in an ADT

When using an Abstract Data Type (ADT), encapsulation and information hiding provide several advantages:

Data Security: Encapsulation and information hiding protect sensitive data from unauthorized access and modification, enhancing data security

Simplified Interfaces: They simplify the interface exposed to external code, reducing complexity and making it easier for users to interact with the ADT

Flexibility: Encapsulation allows for changes to the internal implementation without affecting external code This flexibility is essential for maintaining and evolving software systems.

Encapsulation Using Accessors and Mutators

In object-oriented programming, encapsulation is a key principle that involves concealing an object's implementation details while offering an interface for accessing its properties and behaviors In Java, accessor methods, or getter methods, play a crucial role in encapsulation by enabling the retrieval of an object's private instance variables, thus providing read-only access to its state.

82 | P a g e accessor methods, you can ensure that the object's state is not modified accidentally or maliciously by external code

Mutator methods, or setter methods, enable the modification of an object's private instance variables, offering write-only access to its state They ensure that changes to the object's state occur through a controlled interface, promoting encapsulation and data integrity.

To ensure encapsulation in classes, accessors (getters) and mutators (setters) are commonly utilized Accessors facilitate data reading, while mutators enable data modification This clear distinction between access and modification helps maintain data integrity.

Figure 55: Encapsulation Using Accessors and Mutators

Encapsulation Using Properties

Properties provide a more elegant way to achieve encapsulation In many programming languages, properties allow you to define accessors and mutators while simplifying their usage

In summary, encapsulation is a key principle of object-oriented programming that promotes data security, abstraction, and flexibility When utilized within an Abstract Data Type (ADT), it strengthens data protection and streamlines user interactions with the ADT's interface Additionally, features like Read-only and Write-only properties serve as effective mechanisms for achieving encapsulation and maintaining information hiding in a structured way.

D2 Discuss the view that imperative ADTs are a basis for object orientation and, with justification, state whether you agree

This section examines the viewpoint that imperative Abstract Data Types (ADTs) serve as the cornerstone of object-oriented programming (OOP) and offers a thorough analysis of this stance We will also share our opinion on whether we concur or dissent with this perspective.

Imperative ADTs and Object Orientation

Imperative Abstract Data Types (ADTs) are data structures that combine data with the operations applicable to that data, adhering to the principles of imperative programming In contrast, Object-Oriented Programming (OOP) is a programming paradigm that simulates real-world entities by representing them as objects, which encapsulate both data and behavior.

Integrating Imperative ADTs into an Object-Oriented Paradigm

Imperative ADTs can be integrated into an object-oriented paradigm in several ways:

In Object-Oriented Programming (OOP), encapsulation allows objects to combine data and behavior, enabling the translation of imperative Abstract Data Types (ADTs) into classes For instance, a stack ADT, which includes push and pop operations, can be effectively represented as a Stack class featuring push() and pop() methods.

Inheritance in Object-Oriented Programming (OOP) enables the creation of new classes that inherit attributes and methods from existing classes This feature allows imperative Abstract Data Types (ADTs) to act as base classes for OOP-based ADTs, establishing a solid foundation for developing more specialized data structures.

Polymorphism in Object-Oriented Programming (OOP) allows different class objects to be treated as instances of a shared superclass This capability enables the integration of imperative Abstract Data Types (ADTs) into a polymorphic hierarchy, facilitating the use of various data structures through a common interface, promoting interchangeability and flexibility in programming.

Comparing and Contrasting Traditional Imperative ADTs with OOP-based ADTs

Traditional Imperative ADTs and OOP-based ADTs have similarities and differences:

Encapsulation: Both paradigms encapsulate data and operations

Abstraction: They abstract data manipulation from the underlying implementation

Modularity: Both support modular programming by breaking down complex structures into manageable components

Paradigm: Traditional Imperative ADTs are often associated with procedural programming, while OOP- based ADTs are rooted in the object-oriented paradigm

Inheritance and Polymorphism: OOP-based ADTs can leverage inheritance and polymorphism, allowing for more flexible and extensible designs Traditional ADTs lack these features

Object-Oriented Concepts: OOP-based ADTs leverage object-oriented concepts like classes, objects, inheritance, and polymorphism to create reusable and extensible data structures

Language Support: OOP-based ADTs are typically implemented in languages explicitly designed for OOP (e.g., Java, C++, Python), offering native support for object-oriented features.

Discussion on Imperative ADTs as a Basis for Object Orientation

Imperative Abstract Data Types (ADTs) provide a solid foundation for object-oriented programming (OOP), as both paradigms share core principles that enable an easy transition between them.

Both imperative Abstract Data Types (ADTs) and Object-Oriented Programming (OOP) prioritize encapsulation, which involves combining data and operations into a single unit In imperative ADTs, this encapsulation is achieved through functions, whereas OOP accomplishes it by organizing data and operations within objects and methods.

The historical development of Object-Oriented Programming (OOP) has been shaped by imperative Abstract Data Types (ADTs) Early OOP languages, such as Simula, introduced foundational concepts like classes and objects that closely mirror the principles of imperative ADTs.

Imperative ADTs can be regarded as a transitional stepping stone towards OOP They introduce the crucial concept of encapsulation and data abstraction, which are foundational in OOP

Object-oriented programming (OOP) builds upon the foundation established by imperative abstract data types (ADTs) by incorporating advanced features such as inheritance, polymorphism, and dynamic binding These features enhance the ability to model complex systems, allowing for greater sophistication and flexibility in software design.

Object-oriented programming (OOP) is typically utilized with languages specifically designed for it, including Java, Python, and C++ These languages inherently support classes and objects, delivering a robust framework for OOP that exceeds the functionality of conventional imperative languages.

Imperative Abstract Data Types (ADTs) play a crucial role in the foundational concepts of object-oriented programming (OOP) by emphasizing encapsulation and data abstraction OOP enhances these principles with advanced features and a comprehensive approach to software modeling The historical connection between imperative ADTs and OOP marks a significant milestone in the evolution of object orientation.

Example to prove the viewpoint

Let's provide an example to illustrate the viewpoint that imperative Abstract Data Types (ADTs) can serve as a basis for object orientation

5.2 OOP-based ADT - Stack Class:

In the StackFunctions version, a static context is utilized to manage a stack, reflecting a common method in procedural programming Conversely, the OopBaseADT class introduces an object with attributes and methods, exemplifying encapsulation and core OOP principles This shift underscores the essential connection between imperative Abstract Data Types (ADTs) and object-oriented design.

In summary, imperative Abstract Data Types (ADTs) play a crucial role in establishing the principles of object orientation by highlighting encapsulation and data abstraction Nonetheless, Object-Oriented Programming (OOP) enhances these concepts with advanced features and a comprehensive approach to software modeling Thus, imperative ADTs serve as a foundational step towards OOP, paving the way for a more extensive understanding of object-oriented principles.

Ngày đăng: 13/10/2023, 00:25

TỪ KHÓA LIÊN QUAN

w