You are on page 1of 11

Harambee University

Faculty of Technology
Department of Computer Science
Assignment forData structures & Algorithm
1. Can you provide a detailed explanation of the LIFO concept in the stack data structure?
2. How does a stack differ from a queue in terms of insertion and deletion?
3. Can you explain the usage of push and pop operations in the stack and how they are
implemented?
4. How would you design an algorithm to check if a sequence of parentheses is balanced using
a stack?
5. What complexities are involved while performing insert, delete, and search operations in a
stack?
6. How would you implement two stacks using one array?
7. Could you design a stack that, in addition to push and pop, also has a function min which
returns the minimum element?
8. How would you detect a loop in a linked list using a stack?
9. Can you explain how a stack can be used to reverse a string or a list?
10. Can you explain how the runtime stack is used in function calls?

Due date 4/5/2016


Answers
1. The LIFO (Last-In, First-Out) concept is a fundamental characteristic of the stack
data structure. It means that the most recently added element to the stack is the first
one to be removed. To understand this concept in more detail, let's explore the stack
data structure and its operations.
A stack is a linear data structure that follows the LIFO principle. It can be visualized
as a vertical stack of items, where new items are added or removed from the top of the
stack. The top of the stack represents the most recently added element, while the
bottom represents the least recently added element.
The stack data structure supports two main operations:
1. Push: This operation adds an element to the top of the stack. When a new item
is pushed onto the stack, it becomes the new top element, and the size of the
stack increases by one.
2. Pop: This operation removes the top element from the stack. The element that
was pushed last is the first one to be removed. When an item is popped from the
stack, the size decreases by one, and the element below the removed item
becomes the new top.
These operations maintain the LIFO behavior of the stack. The last element that was
pushed onto the stack is always the first one to be popped off. Think of it as a stack of
plates: the most recently placed plate is the one you can easily access and remove.
Here's an example to illustrate the LIFO concept in a stack:
Consider an empty stack. We perform the following operations:
1. Push 10: The stack becomes [10].
2. Push 5: The stack becomes [10, 5].
3. Push 7: The stack becomes [10, 5, 7].
4. Pop: The top element, 7, is removed. The stack becomes [10, 5] after the
operation.
5. Push 3: The stack becomes [10, 5, 3].
6. Pop: The top element, 3, is removed. The stack becomes [10, 5].
As you can see, the most recently added elements, such as 7 and 3, were the first ones
to be removed from the stack. This demonstrates the LIFO behavior of the stack.
The LIFO concept is particularly useful in scenarios where the order of insertion and
removal of elements is critical, such as function call stack management, expression
evaluation, undo/redo functionality, and backtracking algorithms.
In summary, the LIFO (Last-In, First-Out) concept in the stack data structure ensures
that the element most recently added to the stack is the first one to be removed. The
push operation adds elements to the top of the stack, while the pop operation removes
the top element, maintaining the LIFO behaviour.
2. In terms of insertion and deletion, a stack and a queue are two different data
structures that follow different principles. While a stack follows the LIFO (Last-In,
First-Out) principle, a queue follows the FIFO (First-In, First-Out) principle. Let's
explore the differences in more detail:
Stack:
 Insertion: In a stack, the insertion operation is called "push." It adds a new
element to the top of the stack. The most recently added element becomes the
top, and subsequent elements are pushed down. New elements are always added
on top, regardless of the elements already present in the stack.
 Deletion: In a stack, the deletion operation is called "pop." It removes the top
element from the stack. The most recently added element is the first one to be
removed. When an element is popped, the top of the stack moves down to the
next element.
Queue:
 Insertion: In a queue, the insertion operation is called "enqueue" or "offer." It
adds a new element at the end of the queue. Newly added elements are placed at
the back of the queue, following the existing elements.
 Deletion: In a queue, the deletion operation is called "dequeue" or "poll." It
removes the element at the front of the queue. The element that has been in the
queue for the longest time is the first one to be removed. When an element is
dequeued, the front of the queue moves to the next element.
To summarize:
 Stack: Insertion (push) occurs at the top, and deletion (pop) also occurs at the
top. Last-In, First-Out (LIFO) principle.
 Queue: Insertion (enqueue) occurs at the back, and deletion (dequeue) occurs at
the front. First-In, First-Out (FIFO) principle.

3.! The push and pop operations are fundamental operations in the stack data
structure.:
1. Push Operation:
The push operation adds an element to the top of the stack. It increases the size
of the stack by one and makes the newly added element the new top. Here's an
overview of how the push operation is used:
 Usage:
 To push an element onto the stack, you call the push operation, providing
the element as a parameter.
 For example, if you have a stack named "myStack" and you want to push
the value 10 onto it, you would call: myStack.push(10);
 Implementation:
 The implementation of the push operation involves adding the element to
the top of the stack.
 In an array-based implementation, you would typically have an array to
store the elements, along with a variable to keep track of the top index.
 To push an element, you increment the top index and assign the element
to the corresponding index in the array.
 Here's an example of how the push operation could be implemented in
Python:

python
Copy
def push(stack, element):
stack.append(element)
2. Pop Operation:
The pop operation removes the top element from the stack. It decreases the size
of the stack by one and updates the top to the next element. Here's an overview
of how the pop operation is used:
 Usage:
 To pop an element from the stack, you call the pop operation. The top
element is returned as the result of the operation.
 For example, if you have a stack named "myStack" and you want to pop
an element from it, you would call: poppedElement = myStack.pop();
 Implementation:
 The implementation of the pop operation involves removing the top
element from the stack and returning it.
 In an array-based implementation, you would typically access the
element at the top index, remove it from the array, and decrement the top
index.
 Here's an example of how the pop operation could be implemented in
Python:

python
Copy
def pop(stack):
if not stack:
return None # Handle empty stack case
return stack.pop()
4. To check if a sequence of parentheses is balanced using a stack, you can follow the
following algorithm:
1. Create an empty stack to store the parentheses.
2. Traverse the sequence of parentheses from left to right.
3. For each parentheses encountered, perform the following steps:
 If it is an opening parenthesis (i.e., '(' or '[' or '{'), push it onto the stack.
 If it is a closing parenthesis (i.e., ')' or ']' or '}'), do the following:
 Check if the stack is empty. If it is, return False because there is no
corresponding opening parenthesis.
 Pop the top element from the stack.
 Check if the popped opening parenthesis matches the closing
parenthesis. If they do not match, return False.
4. After traversing the entire sequence, check if the stack is empty. If it is, return
True; otherwise, return False.
Here's a Python implementation of the algorithm:
python
Copy
def is_balanced(parentheses):
stack = []
opening = ['(', '[', '{']
closing = [')', ']', '}']
pairs = {'(': ')', '[': ']', '{': '}'}

for p in parentheses:
if p in opening:
stack.append(p)
elif p in closing:
if not stack:
return False
top = stack.pop()
if pairs[top] != p:
return False

return len(stack) == 0

# Example usage:
sequence = "([{}])"
if is_balanced(sequence):
print("The sequence is balanced.")
else:
print("The sequence is not balanced.")
In this implementation, we use lists opening and closing to store the opening and
closing parentheses, respectively. The time complexity of this algorithm is O(n),
where n is the length of the sequence of parentheses, as it requires a single traversal of
the sequence.

5. In a stack data structure, the complexities involved in performing insert (push),


delete (pop), and search operations are as follows:
1. Insert (Push) Operation:
 Time Complexity: O(1)
 The push operation adds a new element to the top of the stack. It involves
updating the top index or pointer and storing the new element in the
appropriate position. Since the position of insertion is fixed (top of the
stack), the time complexity is constant, regardless of the size of the stack.
2. Delete (Pop) Operation:
 Time Complexity: O(1)
 The pop operation removes the top element from the stack. It involves
updating the top index or pointer to the next element. Similar to the push
operation, the time complexity is constant because the position of
removal is fixed (top of the stack).
3. Search Operation:
 Time Complexity: O(n)
 The stack data structure does not inherently support a search operation, as
it is primarily designed for LIFO access. If you need to search for a
specific element in a stack, you would typically have to perform a linear
search by sequentially popping elements until the desired element is
found or until the stack becomes empty. Therefore, the worst-case time
complexity for searching in a stack is O(n), where n is the number of
elements in the stack.
Generally, the insert (push) and delete (pop) operations in a stack have a constant time
complexity of O(1), while the search operation has a linear time complexity of O(n).

6. Implementing two stacks using one array is possible by dividing the array into two
halves and allocating the stack elements to each half. Here's an outline of the
approach:
1. Start with a fixed-size array and initialize two variables, top1 and top2, as the
indices of the tops of the two stacks. Initially, set top1 to -1 and top2 to the
array size.
2. For the first stack (Stack 1), insert elements from the left end of the array.
Increment top1 by 1 for each insertion.
3. For the second stack (Stack 2), insert elements from the right end of the array.
Decrement top2 by 1 for each insertion.
4. To perform push operations on Stack 1, check if top1 is less than top2 - 1. If
true, it means there is space for Stack 1 to insert elements. Otherwise, Stack 1 is
full.
5. To perform push operations on Stack 2, check if top1 + 1 is less than top2. If
true, it means there is space for Stack 2 to insert elements. Otherwise, Stack 2 is
full.
6. To perform pop operations on Stack 1, check if top1 is greater than or equal to
0. If true, it means Stack 1 has elements to remove. Otherwise, Stack 1 is
empty.
7. To perform pop operations on Stack 2, check if top2 is less than the array size.
If true, it means Stack 2 has elements to remove. Otherwise, Stack 2 is empty.
Here's a Python implementation of the two-stack implementation using an array:
python
Copy
class TwoStacks:
def __init__(self, size):
self.size = size
self.arr = [None] * size
self.top1 = -1
self.top2 = size

def push1(self, data):


if self.top1 < self.top2 - 1:
self.top1 += 1
self.arr[self.top1] = data
else:
print("Stack 1 is full.")

def push2(self, data):


if self.top1 + 1 < self.top2:
self.top2 -= 1
self.arr[self.top2] = data
else:
print("Stack 2 is full.")

def pop1(self):
if self.top1 >= 0:
data = self.arr[self.top1]
self.arr[self.top1] = None
self.top1 -= 1
return data
else:
print("Stack 1 is empty.")
return None

def pop2(self):
if self.top2 < self.size:
data = self.arr[self.top2]
self.arr[self.top2] = None
self.top2 += 1
return data
else:
print("Stack 2 is empty.")
return None

7. To design a stack that supports the push, pop, and min operations, including a
function min() that returns the minimum element in the stack, you can use an
additional auxiliary stack to keep track of the minimum elements at each stage. Here's
an approach to implement such a stack:
1. Create two stacks: main_stack and min_stack. main_stack will hold the actual
stack elements, while min_stack will store the minimum elements at each stage.
2. When pushing an element onto main_stack, check if it is smaller than or equal
to the top element of min_stack. If it is, push the element onto min_stack as
well. This ensures that min_stack always contains the minimum element of the
stack at the top.
3. When popping an element from main_stack, also check if it is equal to the top
element of min_stack. If it is, pop the top element from min_stack as well.
4. To retrieve the minimum element of the stack, simply return the top element
of min_stack.
Here's a Python implementation of the stack with the min() operation:
python
Copy
class MinStack:
def __init__(self):
self.main_stack = []
self.min_stack = []
def push(self, value):
self.main_stack.append(value)
if not self.min_stack or value <= self.min_stack[-1]:
self.min_stack.append(value)

def pop(self):
if not self.main_stack:
return None

value = self.main_stack.pop()
if value == self.min_stack[-1]:
self.min_stack.pop()

return value

def min(self):
if not self.min_stack:
return None
return self.min_stack[-1]

8. To detect a loop in a linked list using a stack, you can utilize a modified depth-first
search (DFS) algorithm. We can use::
1. Create an empty stack and an empty set to keep track of visited nodes.
2. Start traversing the linked list from the head node.
3. For each node encountered, perform the following steps:
 Check if the current node is already in the visited set. If it is, then a loop
is detected. Return True.
 If the current node is not in the visited set, mark it as visited and push it
onto the stack.
4. If the entire linked list is traversed without finding a loop, return False.
Here's a Python implementation of the algorithm:
python
class ListNode:
def __init__(self, value):
self.value = value
self.next = None

def detect_loop(head):
stack = []
visited = set()

current = head
while current:
if current in visited:
return True

visited.add(current)
stack.append(current)
current = current.next

return False

# Example usage:
# Create a linked list with a loop
head = ListNode(1)
head.next = ListNode(2)
head.next.next = ListNode(3)
head.next.next.next = ListNode(4)
head.next.next.next.next = head.next # Creating a loop

if detect_loop(head):
print("Loop detected in the linked list.")
else:
print("No loop detected in the linked list.")

9.

10. Certainly! The runtime stack, also known as the call stack or execution stack, is a
fundamental data structure used by programming languages to manage function calls
and track the execution of a program. It plays a crucial role in maintaining the order of
function calls and managing local variables and their scopes. The followings an
overview of how the runtime stack is used in function calls:
1. When a function is called:
 The current state of the program, including the location where the
function was called (the return address), and the values of local variables,
is pushed onto the stack. This is known as a stack frame or activation
record.
 The parameters of the function are typically passed to the function and
stored in the stack frame.
 The program execution jumps to the beginning of the called function.
2. Inside the function:
 The function code is executed, which may involve further function calls.
 Local variables and any additional parameters are allocated and stored in
the stack frame.
 Intermediate values and calculations are stored on the stack as needed.
3. When a function completes or returns:
 The return value, if any, is stored in a designated location (e.g., a register
or memory location).
 The stack frame of the completed function is removed from the stack,
and the program execution returns to the previous location (the return
address) stored in the stack frame.
 The state of the program is restored to the state before the function call,
including the values of local variables.
4. If there are nested function calls:
 Each function call creates a new stack frame on top of the previous one,
forming a stack of stack frames.
 The stack grows and shrinks as function calls are made and returned.
 The current execution context is always associated with the topmost stack
frame.
The runtime stack ensures that function calls are executed in a Last-In-First-Out
(LIFO) order. This means that the most recently called function is executed first, and
its stack frame is removed last when the function completes.

You might also like