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

14 Stacks Queues

Uploaded by

Shreyam Patel
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)
23 views

14 Stacks Queues

Uploaded by

Shreyam Patel
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/ 45

Stacks and Queues

Two useful ADTs

1
Stacks

2
Basic Definitions

● A stack is an ordered list in which all insertions and all deletions occur at one end of the list.

● That end is called the top of the stack.

● Insertion is called push.

● Deletion is called pop.

● A stack is called a last-in-first-out (LIFO) list.

3
Visualization of a Stack (Last In First Out)

In Out

C B A B C

4
The interface for a stack
A stack ADT can be specified by the following basic operations. We assume that we are maintaining
a stack of characters. In practice, the data type for each element of a stack can be of any data type.

S = init() Initialize S to an empty stack.


isEmpty(S) Returns "true" if and only if the stack S is empty
isFull(S) Returns "true" if and only if the stack S has a bounded size and holds the
maximum number of elements that it can
top(S) Return the element at top of the stack S, or error if the stack is empty
S = push(S, ch) Push the character ch at the top of the stack S
S = pop(S) Pop an element from the top of the stack S
print(S) Print the elements of the stack S from top to bottom

5
Stacks can be implemented

a) Using arrays
b) Using linked list
...

6
Array implementation: Basic Idea

In the array implementation, we would:

• Declare an array of fixed size (which determines the maximum size of the stack).
• Keep a variable top which always points to the “top” of the stack (contains the array
index of the “top” element)
• Both push and pop will happen at the top of the stack
• If we assume that the stack elements are stored in the array starting from the index
0, it is convenient to take the top as the maximum index of an element in the stack.
• The other boundary 0, can in principle be treated as the top, but insertions and
deletions at the location 0 call for too many relocations of array elements.

7
Declaration and Initialization

#define MAXLEN 100 stack init ( )


{
stack S;
typedef struct {
char element [MAXLEN]; S.top = -1;
int top; return S;
}
} stack;

8
Check for empty stack

#define MAXLEN 100 int isEmpty (stack S)


{
typedef struct { return (S.top == -1);
char element [MAXLEN]; }
int top;
} stack;

9
Check if stack is full

#define MAXLEN 100 int isFull (stack S)


{
typedef struct { return (S.top == MAXLEN - 1);
char element [MAXLEN]; }
int top;
} stack;

10
Obtain the top of the stack

char top (stack S)


#define MAXLEN 100 {
if (isEmpty(S)) {
typedef struct { printf("top: Empty stack\n");
char element [MAXLEN]; return '\0’;
int top; }
} stack; return S.element [S.top];
}

11
Push

#define MAXLEN 100 stack push (stack S, char ch)


{
typedef struct { if (isFull(S)) {
char element [MAXLEN]; printf("push: Full stack\n");
int top; return S;
} stack; }
++S.top;
S.element[S.top] = ch;
return S;
}

12
Pop

#define MAXLEN 100 stack pop ( stack S )


{
typedef struct { if (isEmpty(S)) {
char element [MAXLEN]; printf ("pop: Empty stack");
int top; return S;
} stack; }
--S.top;
return S;
}

13
Print the contents of the stack

#define MAXLEN 100 void print (stack S)


{
typedef struct { int i;
char element[MAXLEN];
int top; for (i = S.top; i >= 0; --i)
} stack; printf ("%c", S.element[i]);
}

14
Example main function and its output

Output:

15
Implementation of a stack using a linked list
● A linked list is an ordered list, and can be used to implement a stack.

● A linked list has two ends: head and tail.

● Both insertion and deletion are easy at the head.

● Insertion is easy at the tail if we maintain a tail pointer.

● Deletion is time-consuming even in the presence of a tail pointer.

● So the obvious choice is: The top of the stack is at the head of the linked list.

16
Implementation of the stack ADT functions
typedef struct _node { int isFull ( stack S )
char element; {
return 0;
struct _node *next;
}
} node;
char top ( stack S )
typedef node *stack; {
if (isEmpty(S)) return ‘\0’;
return S -> element;
stack init () }
{
return NULL; void print ( stack S )
} {
while (S) {
int isEmpty ( stack S ) printf("%c", S -> element);
{ S = S -> next;
return (S == NULL); }
} }
17
Implementation of the stack ADT functions (continued)

stack push ( stack S, char ch ) stack pop ( stack S )


{ {
node *p; node *p;

if (isEmpty(S)) {
p = (node *)malloc(sizeof(node));
printf(“pop: empty stack\n”);
p -> element = ch; return S;
p -> next = S; }
return p; p = S;
} S = S -> next;
free(p);
return S;
}

18
Exactly the same main function works

Output:

19
Implementation of a stack using dynamic arrays

typedef struct { stack init ( int n )


int allocsize; {
char *element; stack S;
int top; S.element = (char *)malloc(n * sizeof(char));
} stack; S.allocsize = n;
S.top = -1;
return S;
}

Note: This init() function has a behavior different from the original specifications of the stack ADT. The main
function given earlier will not work. If you need to follow the original ADT specification, you can choose an
initial size (like 64) of the element array, so no user input is required. This implementation is meaningful to
the users who accept the new specification of init(). 20
Implementation of a stack using dynamic arrays

typedef struct { stack push ( stack S, char ch )


int allocsize; {
char *element; ++S.top;
int top; if (S.top >= S.allocsize) {
} stack; S.allocsize *= 2;
S.element = (char *)realloc(S.element,
S.allocsize * sizeof(char));
}
S.element[S.top] = ch;
return S;
}

21
An Application of Stacks:
Parenthesis matching

22
The Basic Problem

Given a parenthesized expression, to test whether the expression is properly parenthesized.


• Whenever a left parenthesis is encountered, it is pushed in the stack.
• Whenever a right parenthesis is encountered, pop from stack and check if the parentheses
match.
• Works for multiple types of parentheses
( ), { }, [ ]

23
Pseudocode
while (not end of string) do {
a = get_next_token();
if (a is ‘(‘ or ‘{‘ or ‘[‘) push (a);
if (a is ‘)’ or ‘}’ or ‘]’) {
if (isempty()) {
printf (”Not well formed”);
exit();
}
x = pop();
if (a and x do not match) {
printf (”Not well formed”);
exit();
}
}
}
if (not isempty()) printf (”Not well formed”);
24
Given expression: (a + (b – c) * (d + e))
Search string for parenthesis from left to right:
(: push (‘(‘) Stack: (
(: push (‘(‘) Stack: ( (
): x = pop() = ( Stack: ( MATCH
(: push (‘(‘) Stack: ( (
): x = pop() = ( Stack: ( MATCH
): x = pop() = ( Stack: EMPTY MATCH

Given expression: (a + (b – c)) * d)


Search string for parenthesis from left to right:
(: push (‘(‘) Stack: (
(: push (‘(‘) Stack: ( (
): x = pop() = ( Stack: ( MATCH
): x = pop() = ( Stack: EMPTY MATCH
): x = pop() = ( Stack: ? MISMATCH

25
Queues

26
Basic Definitions

● A queue is an ordered list in which all insertions occur at one end of the list, and all
deletions occur at the other end of the list.

● The insertion end is called the back or the rear of the queue.

● The deletion end is called the front or the head of the queue.

● Insertion is called enqueue.

● Deletion is called dequeue.

● A queue is called a first-in-first-out (FIFO) list.

27
Visualization of a Queue (First In First Out)

In Out

C B A B A

28
The interface for a queue
The following functions specify the operations on the queue ADT. We are going to maintain a
queue of characters. In practice, each element of a queue can be of any well-defined data type.

Q = init() Initialize the queue Q to the empty queue.


isEmpty(Q) Returns "true" if and only if the queue Q is empty.
isFull(Q) Returns "true" if and only if the queue Q is full, provided that there is a
limit on the maximum size of the queue.
front(Q) Returns the element at the front of queue Q or error if the queue is empty.
Q = enqueue(Q,ch) Inserts the element ch at the back of the queue Q. Insertion request in a
full queue leads to failure along with some appropriate error message.
Q = dequeue(Q) Delete one element from the front of the queue Q. A dequeue attempt
from an empty queue should lead to failure and appropriate error messages.
print(Q) Print the elements of the queue Q from front to back.
29
Queues can be implemented

a) Using arrays
b) Using linked list
...

30
Array implementation of a queue: The basic idea

● We maintain two indices to represent the front and the back of the queue.

● For an enqueue operation, the back index is incremented and the new element is written in
this location.

● For a dequeue operation, on the other hand, the front is simply advanced by one position.

● As elements are enqueued and dequeued, the entire queue moves down the array and the
back index may hit the right end of the array, even when the size of the queue is smaller than
the capacity of the array.

31
Array implementation of a queue: Some points to consider

● To avoid waste of space, we allow our queue to wrap at the end. This means that after the back
pointer reaches the end of the array and needs to proceed further down the line, it comes back
to the zeroth index, provided that there is space at the beginning of the array to accommodate
new elements.

● Thus, the array is now treated as a circular one with index MAXLEN treated as 0,
MAXLEN + 1 as 1, and so on. That is, index calculation is done modulo MAXLEN.

● We still don't have to maintain the total queue size. As soon as the back index attempts to
collide with the front index modulo MAXLEN, the array is considered to be full.

32
Array implementation of a queue: More points to consider

● A little thought reveals that under this wrap-around technology, there is no difference
between a full queue and an empty queue with respect to arithmetic modulo MAXLEN.

● This problem can be overcome if we allow the queue to grow to a maximum size of
MAXLEN – 1. This means we are going to lose one available space, but that loss is
inconsequential.

● Now the condition for full array is that the front index is two locations ahead of the back
modulo MAXLEN, whereas the empty array is characterized by that the front index is just
one position ahead of the back again modulo MAXLEN.

33
Declaration and Initialization

#define MAXLEN 100 queue init ()


{
typedef struct { queue Q;
char element [MAXLEN]; Q.front = 0;
int front; Q.back = MAXLEN - 1;
int back; return Q;
} queue; }

34
Check for empty queue

#define MAXLEN 100 int isEmpty (queue Q)


{
typedef struct { if (Q.front == (Q.back + 1) % MAXLEN)
char element [MAXLEN]; return 1;
int front; else
int back; return 0;
} queue; }

35
Check for full queue

#define MAXLEN 100 int isFull (queue Q)


{
typedef struct { if (Q.front == (Q.back + 2) % MAXLEN)
char element [MAXLEN]; return 1;
int front; else
int back; return 0;
} queue; }

36
Obtain the front of the queue

char front (queue Q)


#define MAXLEN 100
{
if (isEmpty(Q)) {
typedef struct {
printf("front: empty Queue");
char element [MAXLEN];
return '\0’;
int front;
}
int back;
return Q.element [Q.front];
} queue;
}

37
Enqueue

#define MAXLEN 100 queue enqueue (queue Q, char ch) {

typedef struct { if (isFull(Q)) {


char element [MAXLEN]; printf("enqueue: Queue is full\n");
int front; return Q;
int back; }
} queue;
++Q.back;
if (Q.back == MAXLEN) Q.back = 0;
Q.element[Q.back] = ch;
return Q;
}

38
Dequeue

#define MAXLEN 100 queue dequeue (queue Q) {

typedef struct { if (isEmpty(Q)) {


char element [MAXLEN]; printf("dequeue: empty Queue\n");
int front; return Q;
int back; }
} queue;
++Q.front;
if (Q.front == MAXLEN) Q.front = 0;
return Q;
}

39
Print the contents of the queue

#define MAXLEN 100 void print (queue Q) {


int i;
typedef struct { if (isEmpty(Q)) return;
char element [MAXLEN];
int front; i = Q.front;
int back; while (1) {
} queue; printf ("%c", Q.element[i]);
if (i == Q.back) break;
if (++i == MAXLEN) i = 0;
}
}

40
Example main function and its output

Output:

41
Linked-list implementation of a queue
● We need to maintain two pointers to the head and the typedef struct _node {
tail nodes of the list. char element;
struct _node *next;
} node;
● Insertion is easy at both the ends. typedef struct {
node *front;
node *back;
● Deletion is easy at the front, but costly at the tail. } queue;

queue init ()
● So the natural choice is: {
return (queue){NULL,NULL};
}
■ The head of the list is the front of the queue.
■ The tail of the list is the back of the queue.

42
Enqueue and Dequeue
queue enqueue ( queue Q, char ch ) queue dequeue ( queue Q )
{ {
node *p;
node *p;
p = (node *)sizeof(node); if (isEmpty(Q)) {
p -> element = ch; printf(“empty queue\n”);
return Q;
p -> next = NULL;
}
if (Q.back == NULL) {
Q.front = Q.back = p; p = Q.front;
} else { Q.front = Q.front -> next;
if (Q.front == NULL)
Q.back -> next = p; Q.back = NULL;
Q.back = p; free(p);
} return Q;
}
return Q;
}
Exercise: Write the other ADT functions.

43
Practice exercises
1. Make an implementation of the queue ADT using dynamic and realloc()-able array.
2. A double-ended queue (deque) is a queue with the exception that it supports insertion and deletion at
both the ends. Each insert/delete operation must specify the end at which the operation is to be
performed. Implement initialization, insertion, and deletion functions on a double-ended queue using:
a. Static arrays
b. Dynamic arrays
c. Linked lists
3. A dictionary of integers is a set (an unordered collection) of integers that supports initialization, insertion,
deletion, and searching. Implement the dictionary ADT using:
a. Unsorted arrays
b. Unsorted linked lists
c. Sorted arrays
d. Sorted linked lists
4. Suppose that in the dynamic-array implementation of the stack ADT, we want to ensure that the element
array is never less than half full. The init function (no input size) would set element to a NULL pointer.
Rewrite the ADT operations so that the never-less-than-half-full condition is always maintained.
44
Practice exercises
5. Let w be an alphanumeric string.
a. Use the stack ADT calls to check whether a string is of the form w#wr for some w, where wr is the
reverse of w.
b. Use the queue ADT calls to check whether a string is of the form w#w for some w.
6. A (univariate) polynomial with integer coefficients is an ADT that supports initialization (from an array),
check for zero, and adding, subtraction, and multiplication of two polynomials. Note that in order to store
a polynomial, it suffices to know its degree d, and the coefficients of 1, x, x2, x3, . . . , xd. Implement the
polynomial ADT using:
a. Arrays
b. Linked lists
7. A sparse polynomial consists of a very few non-zero terms. We store a sparse polynomial as a list of
(degree, coefficient) pairs for the non-zero terms. The operations to be supported are initialization (from
an array of pairs), addition, subtraction, and multiplication. Implement the sparse polynomial ADT using:
a. Arrays
b. Linked lists
45

You might also like