Beauty Amidst the Bytes_ Unearthing Unexpected Advantages of the Digital Wast...
computer notes - Stack
1. ecomputernotes.com Data Structures Lecture No. 06
___________________________________________________________________
Data Structures
Lecture No. 06
Stack From the Previous Lecture
We started discussing Stack data structure and its implementation in the previous
lecture. We also implemented stack structure using an array and wrote code for its
push(), pop() and top() operations. We realized that we have to specify the size of the
array before using it whether we declare it statically or dynamically. Arrays are of
fixed size and when they become full, no more elements can be added to them. In
order to get to know that the array has gone full, we wrote the isFull() method. It
became the responsibility of the user of the stack structure to call isFull() method
before trying to insert an element using the push() method otherwise the whole
program could crash.
isEmpty() method is implemented as a stack can be empty like a list or set structures.
It is important to understand that isFull() method is there in stack implementation
because of limitation of array but isEmpty() method is part of the stack characteristics
or functionality.
As previously in the implementation of list structure, we used linked list while
allocating nodes dynamicallyin order to avoid the fixed sized limitation of array. Now
in this case also, again to overcome the limitation of array, we are going to make use
of linked list in place of array to implement the stack data structure. Let s see, how we
can implement a stack structure using linked list and how the implementation code
will look like internally.
Stack Using Linked List
We can avoid the size limitation of a stack implemented with an array, with the help
of a linked list to hold the stack elements.
As needed in case of array, we have to decide where to insert elements in the list and
where to delete them so that push and pop will run at the fastest.
Primarily, there are two operations of a stack; push() and pop(). A stack carries lifo
behavior i.e. last in, first out.
You know that while implementing stack with an array and to achieve lifo behavior,
we used push and pop elements at the end of the array. Instead of pushing and
popping elements at the beginning of the array that contains overhead of shifting
elements towards right to push an element at the start and shifting elements towards
Page 1 of 3
2. ecomputernotes.com Data Structures Lecture No. 06
___________________________________________________________________
left to pop an element from the start. To avoid this overhead of shifting left and right,
we decided to push and pop elements at the end of the array.
Now, if we use linked list to implement the stack, where will we push the element
inside the list and from where will we pop the element? There are few facts to
consider, before we make any decision:
- For a singly-linked list, insert at start or end takes constant time using the head and
current pointers respectively. As far as insertion is concerned, it is workable and
equally efficient at the start and end.
- Removing an element at the start is constant time but removal at the end requires
traversing the list to the node one before the last. So removing from the start is
better approach rather than from the end.
Therefore, it makes sense to place stack elements at the start of the list because
insertion and removal take constant time. As we don t need to move back and forth
within the list, therefore, there is no requirement of doubly or circular linked list.
Singly linked list can serve the purpose. Hence, the decision is to insert the element at
the start in the implementation of push operation and remove the element from the
start in the pop implementation.
head
top 1
7 1 7 5 2
5
2
Fig 1. Stack using array (on left side) and linked list (on right side)
There are two parts of above figure.On the left hand, there is the stack implemented
using an array. The elements present inside this stack are 1, 7, 5 and 2. The most
recent element of the stack is 1. It may be removed if the pop() is called at this point
of time. On the right side, there is the stack implemented using a linked list. This
stack has four nodes inside it which are liked in such a fashion that the very first node
pointed by the head pointer contains the value 1. This first node with value 1 is
pointing to the node with value 7. The node with value 7 is pointing to the node with
value 5 while the node with value 5 is pointing to the last node with value 2. To make
a stack data strcuture using a linked list, we have inserted new nodes at the start of the
linked list.
Let s see the code below to implement pop() method of the stack.
int pop()
{
1. int x = head->get();
Page 2 of 3
3. ecomputernotes.com Data Structures Lecture No. 06
___________________________________________________________________
2. Node * p = head;
3. head = head->getNext();
4. delete p;
5. return x;
}
At line 1, we have declared x as an int and retrieved one element from the node of the
stack that is pointed by the head pointer. Remember, the Node class and its get()
method that returns the value inside the node.
At line 2, p is declared as a pointer of type Node and address inside the head pointer is
being saved inside this p pointer.
At line 3, the address of the next node is being retrieved with the help of the getNext()
method of the Node class and being assigned to head pointer. After this assignment,
the head pointer has moved forward and started pointing to the next element in the
stack.
At line 4, the node object pointed by the pointer p is being deallocated (deleted).
At line 5, the function is returning the value of the node retrieved in step 1.
head
top 7 1 7 5 2
5
2
Fig 2. A node removed from the stack after the pop() call
Let s see the code of the push() method of the stack:
void push(int x)
{
1. Node * newNode = new Node();
2. newNode->set(x);
3. newNode->setNext(head);
4. head = newNode;
}
In line 1, a new node is created, using the new Node() statement and returned pointer
is assigned to a pointer newNode. So newNode starts pointing to the newly created
Node object.
In line 2, the value 2 is set into the newly created Node object.
In line 3, the next node of the newly created node is set to the node pointed to by the
head pointer using setNext(head).
Page 3 of 3
4. ecomputernotes.com Data Structures Lecture No. 06
___________________________________________________________________
In line 4, the head pointer is made to point to the newly created node.
head
top 9
7 7 5 2
5
2 newNode 9
Fig 3. A node added to the stack after the push(9) call
These are two primary methods of a stack. By using the push() method, we can keep
on pushing elements and using the pop() methods. Elements can be removed from the
stack till the time, it gets empty. As discussed earlier, isEmpty() is the stack
characteristic but isFull() was implemented because of the size limitation of the array.
We are no more using array to implement a stack. Rather, we have used linked list
here for stack implementation. Therefore, isFull() might not be required here. An
interesting question arises here. Can we add infinite elements to the stack now. We
should remember that this program of stack will run on computer that definitely has a
limited memory. Memory or Address space of a computer is the space (physical
memory and disk space) that can be addressed by the computer which is limited
inlcuding the limited physical memory. Disk space is used as the virtual memory ( we
will not discuss virtual memory in detail here). A computer with 32-bit addressing can
address upto 232-1 memory locations and similarly a computer with 64-bit addressing
can address upto 264-1 addresses. If this address space becomes full, the stack will
definitely be full. However, the stack implementation is not liable for this fullness of
address space and it is the limitation of a computer address space. Therefore, we don t
need to call isFull() before pushing the element. Rather, isEmpty() is called before
poping an element from the stack.
Let s see the remaining methods of the stack while using linked list to implement it.
int top()
{
return head->get();
}
int isEmpty()
{
return ( head == NULL );
}
The above-mentioned methods i.e. top() and isEmpty() are very simple functions. One
statement inside the top() is retrieving the top element (pointed to by the head pointer)
from the stack and returning it back by value. It is important to note that top() is not
removing the element from the stack, but only retrieving it. The one statement inside
Page 4 of 3
5. ecomputernotes.com Data Structures Lecture No. 06
___________________________________________________________________
isEmpty() is a check to see if the head pointer is not pointing to any node and it is
NULL. If the head pointer is NULL that means the stack is empty, the method returns
true otherwise it returns false.
All four operations push(), pop(), top() and isEmpty() take constant time. These are
very simple methods and don t contain loops. They are also not CPU hungry
operation. Also note that we have not written isFull() while implementing stack with
the linked list.
Stack Implementation: Array or Linked List
Since both implementations support stack operations in constant time, we will see
what are the possible reasons to prefer one implementation to the other.
- Allocating and de-allocating memory for list nodes does take more time than pre-
allocated array. Memory allocation and de-allocation has cost in terms of time,
especially, when your system is huge and handling a volume of requests. While
comparing the stack implementation, using an array versus a linked list, it
becomes important to consider this point carefully.
- List uses as much memory as required by the nodes. In contrast, array requires
allocation ahead of time. In the previous bullet, the point was the time required for
allocation and de-allocation of nodes at runtime as compared to one time
allocation of an array. In this bullet, we are of the view that with this runtime
allocation and de-allocation of nodes, we are also getting an advantage that list
consumes only as much memory as required by the nodes of list. Instead of
allocating a whole chunk of memory at one time as in case of array, we only
allocate memory that is actually required so that the memory is available for other
programs. For example, in case of implementing stack using array, you allocated
array for 1000 elements but the stack, on average, are using 50 locations. So, on
the average, 950 locations remain vacant. Therefore, in order to resolve this
problem, linked list is handy.
- List pointers (head, next) require extra memory. Consider the manipulation of
array elements. We can set and get the individual elements with the use of the
array index; we don t need to have additional elements or pointers to access them.
But in case of linked list, within each node of the list, we have one pointer element
called next, pointing to the next node of the list. Therefore, for 1000 nodes stack
implemented using list, there will be 1000 extra pointer variables. Remember that
stack is implemented using singly-linked list. Otherwise, for doubly linked list,
this overhead is also doubled as two pointer variables are stored within each node
in that case.
- Array has an upper limit whereas list is limited by dynamic memory allocation. In
other words, the linked list is only limited by the address space of the machine.
We have already discussed this point at reasonable length in this lecture.
Use of Stack
Examples of uses of stack include- traversing and evaluating prefix, infix and postfix
expressions.
Consider the expression A+B: we think of applying the operator + to the operands
A and B. W e have been writing this kind of expressions right from our primary
classes. There are few important things to consider here:
Page 5 of 3
6. ecomputernotes.com Data Structures Lecture No. 06
___________________________________________________________________
Firstly, + operator requires two operators or in other words + is a binary operator.
Secondly, in the expression A+B, the one operand A is on left of the operator while
the other operand B is on the right side. This kind of expressions where the operator is
present between two operands called infix expressions. We take the meanings of this
expression as to add both operands A and B.
There are two other ways of writing expressions:
− We could write +AB, the operator is written before the operands A and B. These
kinds of expressions are called Prefix Expressions.
− We can also write it as AB+, the operator is written after the operands A and B.
This expression is called Postfix expression.
The prefixes pre and post refer to the position of the operator with respect to the two
operands.
Consider another expression in infix form: A + B * C. It consists of three operands A,
B, C and two operator +,* . We know that multiplication () is done before addition
(+), therefore, this expression is actually interpreted as: A + (B * C). The
interpretation is because of the precedence of multiplication (*) over addition (+). The
precedence can be changed in an expression by using the parenthesis. We will discuss
it a bit later.
Let s see, how can we convert the infix expression A + (B * C) into the postfix form.
Firstly, we will convert the multiplication to postfix form as: A + (B C *). Secondly,
we will convert addition to postfix as: A (B C *) + and finally it will lead to the
resultant postfix expression i.e. : A B C * +. Let s convert the expression (A + B) * C
to postfix. You might have noticed that to overcome the precedence of multiplication
operator (*) we have used parenthesis around A + B because we want to perform
addition operation first before multiplication.
(A + B) * C infix form
(A B +) * C convert addition
(A B +) C * convert multiplication
AB+C* postfix form
These expressions may seem to be difficult to understand and evaluate at first. But
this is one way of writing and evaluating expressions. As we are normally used to
infix form, this postfix form might be little confusing. If a programmer knows the
algorithm, there is nothing complicated and even one can evaluate the expression
manually.
Precedence of Operators
There are five binary operators, called addition, subtraction, multiplication, division
and exponentiation. We are aware of some other binary operators. For example, all
relational operators are binary ones. There are some unary operators as well. These
require only one operand e.g. and +. There are rules or order of execution of
operators in Mathematics called precedence. Firstly, the exponentiation operation is
executed, followed by multiplication/division and at the end addition/subtraction is
done. The order of precedence is (highest to lowest):
Page 6 of 3
7. ecomputernotes.com Data Structures Lecture No. 06
___________________________________________________________________
Exponentiation ↑
Multiplication/division *, /
Addition/subtraction +, -
For operators of same precedence, the left-to-right rule applies:
A+B+C means (A+B)+C.
For exponentiation, the right-to-left rule applies:
A ↑ B ↑ C means A ↑ (B ↑ C)
We want to understand these precedence of operators and infix and postfix forms of
expressions. A programmer can solve a problem where the program will be aware of
the precedence rules and convert the expression from infix to postfix based on the
precedence rules.
Examples of Infix to Postfix
Let s consider few examples to elaborate the infix and postfix forms of expressions
based on their precedence order:
Infix Postfix
A+B AB+
12 + 60 23 12 60 + 23
(A + B)*(C D ) AB+CD *
A ↑ B * C D + E/F A B ↑ C*D E F/+
In the next lecture we will see, how to convert infix to postfix and how to evaluate
postfix form besides the ways to use stack for these operations.
Page 7 of 3