0% found this document useful (0 votes)
2 views

MODULE_2_DSC

The document discusses the implementation of Stack and Queue Abstract Data Types (ADTs) in C, detailing operations like push, pop, and peek for stacks, as well as circular queue management. It explains memory management considerations, including stack overflow and dynamic resizing of arrays. Additionally, it introduces the concept of multiple stacks and queues sharing memory space for efficient resource management.

Uploaded by

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

MODULE_2_DSC

The document discusses the implementation of Stack and Queue Abstract Data Types (ADTs) in C, detailing operations like push, pop, and peek for stacks, as well as circular queue management. It explains memory management considerations, including stack overflow and dynamic resizing of arrays. Additionally, it introduces the concept of multiple stacks and queues sharing memory space for efficient resource management.

Uploaded by

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

In Stack ADT Implementation instead of data being stored in each node, the pointer to data is stored.

The program allocates memory for the data and address is passed to the stack ADT.

The head node and the data nodes are encapsulated in the ADT. The calling function can only see the
pointer to the stack.

The stack head structure also contains a pointer to top and count of number of entries currently in
stack.

• push() – Insert an element at one end of the stack called top.


• pop() – Remove and return the element at the top of the stack, if it is not empty.
• peek() – Return the element at the top of the stack without removing it, if the stack is not
empty.
• size() – Return the number of elements in the stack.
• isEmpty() – Return true if the stack is empty, otherwise return false.
• isFull() – Return true if the stack is full, otherwise return false.

Recursion in C programming involves a function calling itself to solve a smaller part of the problem
until a base condition is met. This process uses the system stack in several key ways:
• Function Calls and the Stack: Each time a recursive function is called, a new instance of that
function is placed on the stack. This instance contains the function’s local variables and the
return address.
• Working with the Stack Frame:

Creating a Stack Frame: A stack frame is created when a function is called. This frame
stores the function’s local variables and the return address.

Stack Frame for Each Recursive Call: Each recursive call creates its own stack frame
on top of the previous one. This is essential for the function to remember its state
and variables for each call.

• Base Case and Unwinding:

Base Case: This is a condition in the recursive function that does not make a
recursive call. Reaching the base case is crucial to stop the recursion.

Unwinding the Stack: Once the base case is reached, the function starts returning,
and its stack frames are popped off the stack. This unwinding continues until the
original call is reached and the stack is empty.

• Memory Considerations:

Stack Overflow: If the recursive calls are too deep or the base case is not properly
defined, it can lead to a stack overflow, where the stack space allocated for the
program is exhausted.

Optimization: Tail recursion, where the recursive call is the last operation in the
function, can be optimized by compilers to reuse stack frames, thus saving memory.

Refer to: https://dotnettutorials.net/lesson/how-recursion-uses-stack-


c/#:~:text=Recursion%20in%20C%20programming%20involves,is%20placed%20on%20the%20stack.
For example,
Static Array Implementation:
#include <stdio.h>
#include <stdlib.h>
#define MAX_SIZE 100 // Maximum size of the stack
typedef struct {
int array[MAX_SIZE];
int top;
} Stack;
void initialize_stack(Stack* stack) {
stack->top = -1; // Empty stack
}
void push(Stack* stack, int value) {
if (stack->top == MAX_SIZE - 1) {
printf("Stack overflow! Cannot push %d\n", value);
return;
}
stack->array[++stack->top] = value;
}
int pop(Stack* stack) {
if (stack->top == -1) {
printf("Stack underflow!\n");
exit(EXIT_FAILURE);
}
return stack->array[stack->top--];
}
int is_empty(Stack* stack) {
return stack->top == -1;
}
int peek(Stack* stack) {
if (stack->top == -1) {
printf("Stack is empty!\n");
exit(EXIT_FAILURE);
}
return stack->array[stack->top];
}
int main() {
Stack stack;
initialize_stack(&stack);
push(&stack, 10);
push(&stack, 20);
push(&stack, 30);
printf("Top element: %d\n", peek(&stack));
printf("Popped element: %d\n", pop(&stack));
printf("Popped element: %d\n", pop(&stack));
printf("Is stack empty? %s\n", is_empty(&stack) ? "Yes" : "No");
printf("Popping the last element:\n");
pop(&stack);
printf("Is stack empty? %s\n", is_empty(&stack) ? "Yes" : "No");
return 0;
}
Output:
Top element: 30
Popped element: 30
Popped element: 20
Is stack empty? No
Popping the last element:
Is stack empty? Yes
=== Code Execution Successful ===
Dynamic Array Implementation:
#include <stdio.h>
#include <stdlib.h>
#define INIT 2 // Initial capacity of the dynamic array

typedef struct {
int *array;
int top;
int capacity;
} Stack;
Stack* create_stack() {
Stack* stack = (Stack*)malloc(sizeof(Stack));
stack->array = (int*)malloc(INIT* sizeof(int));
stack->top = -1; // Empty stack
stack->capacity = INIT;
return stack;
}
void resize(Stack* stack) {
stack->capacity *= 2;
stack->array = (int*)realloc(stack->array, stack->capacity * sizeof(int));
}
void push(Stack* stack, int value) {
if (stack->top == stack->capacity - 1) {
resize(stack); // Resize the array if it's full
}
stack->array[++stack->top] = value;
}
int pop(Stack* stack) {
if (stack->top == -1) {
printf("Stack underflow!\n");
exit(EXIT_FAILURE);
}
return stack->array[stack->top--];
}
int is_empty(Stack* stack) {
return stack->top == -1;
}
int peek(Stack* stack) {
if (stack->top == -1) {
printf("Stack is empty!\n");
exit(EXIT_FAILURE);
}
return stack->array[stack->top];
}
void free_stack(Stack* stack) {
free(stack->array);
free(stack);
}
int main() {
Stack* stack = create_stack();
push(stack, 10);
push(stack, 20);
push(stack, 30);
printf("Top element: %d\n", peek(stack));
printf("Popped element: %d\n", pop(stack));
printf("Popped element: %d\n", pop(stack));
printf("Is stack empty? %s\n", is_empty(stack) ? "Yes" : "No");
printf("Popping the last element:\n");
pop(stack);
printf("Is stack empty? %s\n", is_empty(stack) ? "Yes" : "No");
free_stack(stack);
return 0;
}
Output:
Top element: 30
Popped element: 30
Popped element: 20
Is stack empty? No
Popping the last element:
Is stack empty? Yes

=== Code Execution Successful ===


As jobs enter and leave the system, the queue gradually shifts to the right. This means that
eventually the rear index equals MAX_QUEUE_SIZE - 1, suggesting that the queue is full. In this case,
queue-full should move the entire queue to the left so that the first element is again at queue [0] and
front is at - 1. It should also recalculate rear so that it is correctly positioned.
Shifting an array is very time-consuming, particularly when there are many elements in it. We can
obtain a more efficient queue representation if we regard the array queue[MAX_QUEUE_SIZE] as
circular. In this representation, we initialize front and rear to 0 rather than -1. The front index always
points one position counterclockwise from the first element in the queue. The rear index points to
the current end of the queue. The queue is empty if front=rear.
Figure 3.6 shows empty and nonempty circular queues for MAX_QUEUE_SIZE = 6. Figure 3.7
illustrates two full queues for MAX-QUEUE-SIZE = 6. While these have space for one more element,
the addition of such an element will result in front = rear and we won’t be able to distinguish
between an empty and a full queue. So, we adopt the convention that a circular queue of size MAX-
QUEUE-SIZE will be permitted to hold at most MAX-QUEUE-SIZE - 1 elements.
Steps to double queue capacity:
Allocate a new array with twice the size of current array / use realloc()
Copy elements from the old array to the new array in the correct order
Update the queue's capacity and reset the indices (front and rear) to fit the new array layout.
Deallocate the memory used by the old array to avoid memory leaks (if realloc() is not used)
Function when realloc is not used:
void double_capacity(Queue *queue)
{
int new_capacity = queue->capacity * 2;
int *new_array = (int *)malloc(new_capacity * sizeof(int)); // Copy elements in order
for (int i = 0; i < queue->size; i++)
{
new_array[i] = queue->array[(queue->front + i) % queue->capacity];
}
free(queue->array); // Free old array memory
queue->array = new_array;
queue->capacity = new_capacity;
queue->front = 0;
queue->rear = queue->size - 1;
}
Using realloc():
void double_capacity(Queue *queue) {
int old_capacity = queue->capacity;
queue->capacity *= 2;
queue->array = (int *)realloc(queue->array, queue->capacity * sizeof(int));
if (!queue->array) {
printf("Memory allocation failed!\n");
exit(0);
}
if (queue->front > queue->rear)
{
for (int i = 0; i < queue->front; i++)
{
queue->array[i + old_capacity] = queue->array[i];
}
queue->rear += old_capacity;
}
queue->front = 0;
}
Multiple Stacks:
• Definition: Multiple stacks refer to having more than one stack in memory, often managed
independently. Each stack operates separately but shares the same memory space.
• Features:
o Fixed Size: Each stack typically has a fixed size and cannot grow beyond it.
o Separate Pointer for Each Stack: Each stack maintains its own top pointer to track
the top of the stack.
o Memory Efficiency: They share a common memory, reducing memory overhead.
o LIFO (Last-In-First-Out): Stacks operate in a last-in, first-out manner.
Example of Multiple Stacks:
Suppose we have a shared memory space of size 1000. We create three separate stacks, Stack1,
Stack2, and Stack3, each having a maximum size of 300.
• Stack1: Push elements (1, 2, 3) onto Stack1. After each push, the top pointer moves upward.
• Stack2: Push elements (4, 5, 6) onto Stack2. Stack2 uses its own top pointer.
• Stack3: Push elements (7, 8, 9) onto Stack3 independently.
Multiple Queues:
• Definition: Multiple queues refer to having several queues operating independently, typically
within the same memory space.
• Features:
o FIFO (First-In-First-Out): Queues operate in a first-in, first-out manner.
o Separate Front and Rear Pointers: Each queue has its own front and rear pointers.
o Circular Buffer: Often implemented using circular buffers to reuse memory
efficiently.
o Memory Efficiency: Share a common memory space.
Example of Multiple Queues:
Suppose we have a shared memory space of size 1000. We create three queues, Queue1, Queue2,
and Queue3.
• Queue1: Enqueue elements (1, 2, 3) from the front and rear.
• Queue2: Enqueue elements (4, 5, 6).
• Queue3: Enqueue elements (7, 8, 9).
In both cases, multiple stacks and queues help organize and manage resources efficiently in memory.
(Multiple Queues are not given in TB, refer Geeksforgeeks or AI)

You might also like