Thanks to visit codestin.com
Credit goes to www.scribd.com

0% found this document useful (0 votes)
8 views89 pages

Adt

Uploaded by

anish
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PPTX, PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
8 views89 pages

Adt

Uploaded by

anish
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PPTX, PDF, TXT or read online on Scribd
You are on page 1/ 89

ADT is a collection of data and a set of operations on that

data.
There are several operations that are essential when using
an ADT
» finding an item already stored
» adding a new item
» deleting an item.
1. Stacks
A stack is a linear data structure that follows the LIFO (Last In, First Out)
principle.
Characteristics of a Stack
•Push: Adds an item to the top of the stack.
•Pop: Removes and returns the item from the top of the stack.
•Peek (or Top): Returns the top item without removing it.
•IsEmpty: Checks if the stack is empty.
•IsFull: Checks if the stack is full (for array implementation)
Implementation of Stacks
Stacks can be implemented using:
1.Arrays (Static Stack)
1.Fixed size.
2.Uses an index (top pointer) to track the top element.
2.Linked Lists (Dynamic Stack)
1.No fixed size.
2.Each element (node) stores data and a pointer to the next element.
2. Queues
A queue is a linear data structure that follows the FIFO (First In, First Out)
principle.
Characteristics of a Queue
•Enqueue: Adds an item to the rear.
•Dequeue: Removes an item from the front.
•Peek (Front): Returns the front item without removing it.
•IsEmpty: Checks if the queue is empty.
•IsFull: Checks if the queue is full (for array implementation).
Types of Queues

1.Linear Queue
1.Items are processed in order (FIFO).
2.Uses an array or linked list.
2.Circular Queue
1.Prevents unused space in a linear queue.
2.Rear connects to the front in a circular manner.
3.Priority Queue
1.Elements are dequeued based on priority rather than arrival time.
4.Deque (Double-Ended Queue)
1.Insertion and deletion can occur at both ends.
stack = [None for index in range(0,10)]
basePointer = 0
topPointer = -1
stackFull = 10
item = None

This line creates a size 10 with all elements initialized to None


1.stack:
•A list initialized with None values, representing the stack.
•The size is fixed at 10 ([None, None, ..., None]).
2.basePointer:
•Set to 0, representing the base (or start) of the stack.
3.topPointer:
•Initialized to -1, indicating that the stack is empty.
•This pointer will track the position of the topmost item in the stack.
4.stackFull:
•Represents the maximum capacity of the stack, which is 10 in this case.
5.item:
•This is a placeholder for any item to be pushed onto or popped from the stack.
Push Operation (Adding an item to the stack)

def push(item):
global topPointer, stack, stackFull
if topPointer < stackFull - 1: # Check if the stack is not full
topPointer += 1 # Move topPointer to the next empty position
stack[topPointer] = item # Place the item at that position
else:
print("Stack is full. Cannot push.")
Pop Operation (Removing an item from the stack)

def pop():
global topPointer, stack
if topPointer >= 0: # Check if the stack is not empty
item = stack[topPointer] # Retrieve the item from the top of the stack
topPointer -= 1 # Move topPointer down to the next item
return item # Return the popped item
else:
print("Stack is empty. Cannot pop.")
return None
queue = [None for index in range(0,10)]
frontPointer = 0
rearPointer = -1
queueFull = 10
queueLength = 0
def enQueue(item):
global queueLength, rearPointer, queueFull, queue
if queueLength < queueFull: # Check if queue is not full
if rearPointer < len(queue) - 1:
rearPointer += 1 # Move to the next position
else:
rearPointer = 0 # Wrap around (circular queue logic)
queue[rearPointer] = item # Insert the item
queueLength += 1 # Increase queue size
else:
print("Queue is full, cannot enqueue")
def deQueue():
global queueLength, frontPointer, queue
if queueLength > 0: # Check if queue is not empty
item = queue[frontPointer] # Get the front item
queue[frontPointer] = None # Remove the item (optional)
if frontPointer < len(queue) - 1:
frontPointer += 1 # Move to the next position
else:
frontPointer = 0 # Wrap around (circular queue)
queueLength -= 1 # Decrease queue size
return item
else:
print("Queue is empty, cannot dequeue")
return None
# Stack implementation using list
stack = []

# Push operation
stack.append(10)
stack.append(20)
stack.append(30)

print("Stack after pushing:", stack)

# Pop operation
print("Popped element:", stack.pop())
print("Stack after popping:", stack)
3. Linked Lists
A linked list is a dynamic data structure where each element (node) contains:
•Data: The value stored in the node.
•Pointer: A reference to the next node.
Types of Linked Lists
1.Singly Linked List
1.Each node has a pointer to the next node.
2.Doubly Linked List
1.Each node has pointers to both the next and previous nodes.
3.Circular Linked List
1.The last node points back to the first node.
a) Singly Linked List
Each node points to the next node in the sequence. The last node points to NULL.
📌 Example: A → B → C → NULL
b) Doubly Linked List
Each node has two pointers:
•One pointing to the next node.
•One pointing to the previous node.
📌 Example: NULL ← A ↔ B ↔ C → NULL
c) Circular Linked List
The last node points back to the first node, forming a loop.
📌 Example: A → B → C → A (circular connection)
Basic Operations on a Singly Linked List
a) Creating a Linked List
1.Define a node structure (data + next).
2.Allocate memory dynamically.
b) Traversing a Linked List
•Start at the head node.
•Move to the next node until NULL is reached.
c) Insertion
•At the beginning: Change the head pointer.
•At the end: Traverse till the last node, update the next pointer.
•At a given position: Adjust pointers accordingly.
d) Deletion
•First node: Update head pointer.
•Last node: Find second-last node, update its next pointer to NULL.
•Middle node: Adjust pointers to skip the deleted node.
Advantages & Disadvantages of Linked Lists
✅ Advantages:
✔ Efficient memory usage (no need for contiguous memory).
✔ Easy insertion/deletion without shifting elements.
❌ Disadvantages:
✖ More memory required (pointers).
✖ Slower access time (sequential search needed).
Applications of Linked Lists
🔹 Implementing stacks & queues
🔹 Undo/redo functionality
🔹 Memory management (dynamic allocation)
Operations on a Linked List
•Insertion (at the beginning, middle, or end)
•Deletion (removal of a specific node)
•Traversal (moving through the list)
What is a Linked List?
A linked list is a dynamic data structure consisting of nodes, where each node
contains:
1.Data – The actual value stored.
2.Pointer – A reference to the next node in the list.
Unlike arrays, linked lists:
✅ Do not require contiguous memory allocation.
✅ Can grow or shrink dynamically.
✅ Use pointers for efficient insertion and deletion.
Types of Linked Lists
1.Singly Linked List – Each node points to the next node only.
2.Doubly Linked List – Each node has pointers to both the next and previous
nodes
3.Circular Linked List – The last node points back to the first node.
Operations on a Linked List
💡 Insertion
•At the beginning, middle, or end by updating pointers.
💡 Deletion
•Removing a node by adjusting the previous node’s pointer.
💡 Traversal
•Moving through the list using pointers.
DECLARE mylinkedList ARRAY[0:11] OF INTEGER
DECLARE myLinkedListPointers ARRAY[0:11] OF INTEGER
•myLinkedList stores the actual values of the linked list.
•myLinkedListPointers is an array where each element stores the index of the
next node, implementing a linked list structure within an array.
DECLARE startPointer : INTEGER
DECLARE heapStartPointer : INTEGER
DECLARE index : INTEGER
•startPointer: Points to the first element of the linked list. It starts at -1
(indicating an empty list).
•heapStartPointer: Points to the next free node in the heap (initially set to 0
to indicate that memory is free from index 0).
heapStartPointer ← 0
startPointer ← -1 // list empty
FOR index ← 0 TO 11
myLinkedListPointers[index] ← index + 1
NEXT index

•The heap (unused memory space) starts at index 0.


•The startPointer is set to -1, meaning the linked list is empty.

•Each myLinkedListPointers[index] stores the index of the next free


space.
•This creates a free space list (heap), where heapStartPointer always
points to the first free index.
myLinkedListPointers[11] ← -1
The last element (index 11) is set to -1, indicating the end of the free list.

How This Works in Practice


•When a new node is added, it will be taken from the heap (starting at
heapStartPointer).
•When a node is deleted, it is returned to the heap.
•startPointer is updated to track the head of the actual linked list.
This method avoids dynamic memory allocation and is useful in embedded systems or low-
level programming where memory is limited.
myLinkedList = [27, 19, 36, 42, 16, None, None, None, None, None, None, None]
myLinkedListPointers = [-1, 0, 1, 2, 3, 6, 7, 8, 9, 10, 11, -1]
startPointer = 4 # Points to the first element in the list
nullPointer = -1 # Represents the end of the linked list
def find(itemSearch):
found = False
itemPointer = startPointer # Start from the first node

while itemPointer != nullPointer and not found:


if myLinkedList[itemPointer] == itemSearch:
found = True
else:
itemPointer = myLinkedListPointers[itemPointer] # Move to next node
return itemPointer if found else -1 # Return -1 if item not found

# Enter item to search for


item = int(input("Please enter item to be found: "))
result = find(item)

if result != -1:
print(f"Item {item} found at index {result}")
else:
print("Item not found")
1. Variable Declarations
DECLARE itemAdd : INTEGER // The item to be added to the list
DECLARE startPointer : INTEGER // Points to the first node in the list
DECLARE heapStartPointer : INTEGER // Points to the next free space in the heap
DECLARE tempPointer : INTEGER // Temporary pointer for swapping values
CONSTANT nullPointer = -1 // Indicates the end of the list or an empty state

•startPointer: Tracks the first node in the linked list.


•heapStartPointer: Points to the first available free space (from the heap).
•nullPointer: Special value -1, used to represent an empty or end-of-list
condition
•Takes itemAdd as the value to insert.
•If heapStartPointer = -1, there is no free space left in the heap.
•The program prints "Linked list full" and exits.

Stores the current first node's index in tempPointer because the new node will take its place.
•The startPointer is updated to point to the first free space in the heap (next
available index).
•This means the new node will be inserted at this position.
plaintext
•Updates heapStartPointer to point to the next free node in the heap.
•This ensures that the heap always points to the next available memory location.
•Stores the new item in the linked list at startPointer.
plaintext

•Links the newly inserted node to the previous start node.


•The myLinkedListPointers array maintains the link between nodes.
myLinkedList = [27, 19, 36, 42, 16, None, None, None, None, None, None, None]
myLinkedListPointers = [-1, 0, 1, 2, 3, 6, 7, 8, 9, 10, 11, -1]
startPointer = 4 // Points to 16
heapStartPointer = 5 // Points to first free space

Adding 50
1.Check if list is full → heapStartPointer is not -1, so continue.
2.Store current start: tempPointer = startPointer (4)
3.Set new start: startPointer = heapStartPointer (5)
4.Update heap: heapStartPointer = myLinkedListPointers[5] = 6
5.Insert value: myLinkedList[5] = 50
6.Link new node: myLinkedListPointers[5] = 4 (previous
startPointer)
myLinkedList = [27, 19, 36, 42, 16, 50, None, None, None, None, None, None]
myLinkedListPointers = [-1, 0, 1, 2, 3, 4, 7, 8, 9, 10, 11, -1]
startPointer = 5 // 50 is now the first node
heapStartPointer = 6 // Next free space
What is a Binary Tree?
A binary tree is a hierarchical data structure where each node has at most two child nodes,
referred to as the left child and the right child.
Key Terminology:
•Root: The top node of the tree.
•Parent: A node that has child nodes.
•Child: A node that descends from another node.
•Leaf: A node with no children.
•Subtree: A portion of the tree that consists of a node and its descendants.
•Depth: The level of a node from the root (root has depth 0).
•Height: The longest path from a node to a leaf.
•Balanced Tree: A binary tree where the height difference between left and right subtrees is minimal.
Finding an item in a binary tree
Recursion is a process using a function or procedure that
is defined in
terms of itself and calls itself

Key Features of Recursion:


1.Base Case – The condition that stops the recursion. Without it, the function
would call itself indefinitely, leading to a stack overflow.
2.Recursive Case – The part of the function that calls itself with a smaller or
simpler input.

Example of Recursion: Factorial Function


Factorial of a number n(denoted as n!) is calculated as:
n!=n×(n−1)×(n−2)×...× 1
FUNCTION factorial (number : INTEGER)
RETURNS INTEGER
IF number = 0
THEN
answer ← 1 // base case
ELSE
answer ← number * factorial (number - 1)
// recursive call with general case
ENDIF
def factorial(n): RETURN answer
ENDFUNCTION
if n == 0: # Base case
return 1
else:
return n * factorial(n - 1) # Recursive case

print(factorial(5)) # Output: 120


Advantages of Recursion:
✔️Useful for problems that have a natural recursive structure (e.g., trees,
graphs, divide and conquer algorithms).
✔️Leads to shorter, cleaner code for problems like sorting (e.g., quicksort,
merge sort).
Disadvantages of Recursion:
❌ Can be inefficient due to high memory usage (stack frames).
❌ Might lead to stack overflow if the recursion depth is too high.
With recursive functions, the statements after the
recursive function call are not executed until the base
case is reached; this is called winding. After the base
case is reached and can be used in the recursive
process, the function is unwinding
Winding Phase (Recursive Calls)
•The function calls itself repeatedly, reducing the value of n by 1 each time.
•It keeps doing this until it reaches the base case (n = 0).
•Each function call is added to the call stack.

•Base Case (Stopping Condition)


•When factorial(0) is called, it returns 1. This is the base case, preventing
infinite recursion.
•Unwinding Phase (Returning Values)
•The function starts returning values up the stack, computing the results
step by step.
DEFINE FUNCTION compoundInt(principal, rate, years : REAL) RETURNS REAL
IF years = 0 THEN
RETURN principal
ELSE
RETURN compoundInt(principal * (1 + rate), rate, years - 1)
ENDIF
ENDFUNCTION

•Base Case: If years = 0, the function returns the principal (no interest applied).
•Recursive Case: The function multiplies principal by rate and calls itself with
years - 1, effectively compounding the interest recursively.
compoundInt(100, 0.05, 3)
Step-by-Step Recursive Calls
1.compoundInt(100, 0.05, 3)
→ Calls compoundInt(100 * 1.05, 0.05, 2)
→ compoundInt(105, 0.05, 2)
2.compoundInt(105, 0.05, 2)
→ Calls compoundInt(105 * 1.05, 0.05, 1)
→ compoundInt(110.25, 0.05, 1)
3.compoundInt(110.25, 0.05, 1)
→ Calls compoundInt(110.25 * 1.05, 0.05, 0)
→ compoundInt(115.7625, 0.05, 0)
4.compoundInt(115.7625, 0.05, 0)
→ Base case reached
→ Returns 115.7625
 Recursion simplifies complex problems by breaking them into smaller subproblems, making it useful for algorithms like

binary search, mergesort, and tree traversal.

 It provides elegant solutions for mathematical computations (e.g., GCD, Fibonacci) and is essential for graph and tree-

based algorithms like DFS.

 By leveraging the call stack, recursion reduces the need for explicit loops but can lead to higher memory usage and

potential stack overflow.


HOW A COMPILER IMPLEMENTS RECURSION

A compiler implements recursion using the call stack, where each recursive call creates a stack frame storing

variables, parameters, and the return address. When a function returns, its frame is popped off the stack. Some

compilers optimize tail recursion by converting it into iteration, reducing memory usage. Additionally, compilers

may transform recursion into loops to improve performance and prevent stack overflow
What is Big O Notation?
Big O notation is used in computer science to describe the efficiency of algorithms, particularly in terms of time complexity
(speed) and space complexity (memory usage). It provides an upper bound on how the running time of an algorithm
increases as the input size grows.
Common Big O Complexities
1.O(1) – Constant Time
1. The runtime remains the same, regardless of input size.
2. Example: Accessing an element in an array by index.
O(log n) – Logarithmic Time
•The runtime grows slowly as input increases.
•Example: Binary search

O(n) – Linear Time


The runtime increases directly with input size.
Example: Linear search.

O(n log n) – Linearithmic Time


•Common in efficient sorting algorithms like Merge Sort and
Quick Sort.
•Example: Merge Sort.
O(n²) – Quadratic Time
•Common in inefficient sorting algorithms like Bubble Sort and
Insertion Sort.

O(2ⁿ) – Exponential Time


•The runtime doubles with each additional input.
•Example: Recursive Fibonacci sequence.

O(n!) – Factorial Time


•Extremely inefficient, often seen in brute force algorithms.
•Example: Solving the Traveling Salesman Problem (TSP) using
brute force.
Why is Big O Important?
•Helps compare algorithms and choose the most efficient one.
•Allows predicting performance on large data sets.
•Optimizes resource usage (CPU & memory).
A programming paradigm is a set of programming
concepts.
2 different programming paradigms: low-level and imperative (procedural)
programming.
• Low-level programming uses instructions from the computer’s basic
instruction set.
• Assembly language and machine code both use low-level instructions.
• This type of programming is used when the program needs to make use of
specific addresses and registers in a computer, for example when writing a
• In imperative programming, the steps required to execute a program
printer driver.
are set out in the order they need to be carried out.
• This programming paradigm is often used in the early stages of teaching
programming.
Object-Oriented Programming (OOP)

OOP is a programming paradigm that organizes code into objects, which are instances of
classes. It is widely used in languages like Python, Java, and C++ to improve code modularity
and reusability.
Class
•A class is a blueprint for creating objects.
•It defines properties (attributes) and behaviors (methods).
Object
•An object is an instance of a class.
•It has its own values for attributes and can use methods.

class employee:
def __init__ (self, name, staffno):
self.name = name
self.staffno = staffno
def showDetails(self):
print("Employee Name " + self.name)
print("Employee Number " , self.staffno)
myStaff = employee("Eric Jones", 72)
myStaff.showDetails()
• Data hiding protects the integrity of an object by restricting access to
the data and methods within that object.
• One way of achieving data hiding in OOP is to use encapsulation.
• Data hiding reduces the complexity of programming and increases data
protection and the security of data

class employee:
def __init__(self, name, staffno):
self.__name = name
self.__staffno = staffno
def showDetails(self):
print("Employee Name " + self.__name)
print("Employee Number " , self.__staffno)
Inheritance

Inheritance allows a class to acquire attributes and methods from another class.

Types of Inheritance

Single Inheritance

One class inherits from another.

Multiple Inheritance

A class can inherit from multiple parent classes.


class employee: class fullTime(employee):
def __init__ (self, name, staffno): def __init__(self, name, staffno):
self.__name = name employee.__init__(self, name, staffno)
self.__staffno = staffno
self.__fullTimeStaff = True
self.__fullTimeStaff = True
self.__yearlySalary = 0
def showDetails(self):
print("Employee Name " + self.__name) def getYearlySalary (self):
print("Employee Number " , self.__staffno) return(self.__yearlySalary)
class partTime(employee): permanentStaff = fullTime("Eric Jones", 72)
def __init__(self, name, staffno):
permanentStaff.showDetails()
employee.__init__(self, name, staffno)
temporaryStaff = partTime ("Alice Hue", 1017)
self.__fullTimeStaff = False
self.__hoursWorked = 0 temporaryStaff.showDetails ()

def getHoursWorked (self):


return(self.__hoursWorked)
In OOP, the basic methods used during the life of an object can
be divided into
these types
1. Constructors
A constructor is a special method used to initialize an object's attributes when it is created.
•In Python, the constructor method is called __init__().
•In Java, the constructor has the same name as the class.

Example (Python)
class Car:
def __init__(self, brand, speed): # Constructor method
self.brand = brand
self.speed = speed

car1 = Car("Toyota", 120) # Creates an object with initial values


print(car1.brand) # Output: Toyota
2. Getters & Setters
•Getters allow access to private attributes.
•Setters allow modification of private attributes while maintaining control over changes.
•In Python, we use properties or @property decorators.
•In Java, we use public getter and setter methods.
Example (Python) class Car:
def __init__(self, brand, speed):
self.__brand = brand # Private attribute
self.__speed = speed # Private attribute

@property
def brand(self): # Getter
return self.__brand

@brand.setter
def brand(self, new_brand): # Setter
self.__brand = new_brand

car1 = Car("Toyota", 120)


print(car1.brand) # Output: Toyota
car1.brand = "Honda" # Modify attribute using setter
print(car1.brand) # Output: Honda
3. Destructors
A destructor is a method that is called when an object is deleted or goes out of scope.
•In Python, the destructor is __del__().
•In Java, garbage collection handles object destruction automatically (no need for a
destructor).
Example (Python)
class Car:
def __init__(self, brand):
self.brand = brand
print(f"Car {brand} created!")

def __del__(self): # Destructor


print(f"Car {self.brand} is being destroyed!")

car1 = Car("Toyota")
del car1 # Manually deleting the object
# Output: "Car Toyota is being destroyed!"
Binary trees are best implemented using
objects, constructors, containment, functions,
procedures and recursion.
» Objects – tree and node
» Constructor – adding a new node to the tree
» Containment – the tree contains nodes
» Function – search the binary tree for an item
» Procedure – insert a new item in the binary tree
Declarative programming is used to extract knowledge by the
use of queries
from a situation with known facts and rules. It can be argued
that SQL uses declarative programming.

Declarative programming uses statements of facts and rules together with


a
mechanism for setting goals in the form of a query. A fact is a ‘thing’ that
is
known, and rules are relationships between facts. In imperative
programming,

You might also like