You are on page 1of 41

Higher Nationals in Computing

Unit 19: Data Structures and Algorithms


ASSIGNMENT 1

Assessor name: PHAN MINH TAM

Learner’s name: NGUYEN MANH HIEP LONG


ID:GCS18901
Class:
Subject code: 1649

Assignment due: Assignment submitted:


ASSIGNMENT 1 FRONT SHEET

Qualification BTEC Level 5 HND Diploma in Computing

Unit number and title Unit 19: Data Structures and Algorithms

Submission date Date Received 1st submission

Re-submission Date Date Received 2nd submission

Student Name Student ID

Class Assessor name Phan Minh Tam

Student declaration
I certify that the assignment submission is entirely my own work and I fully understand the consequences of plagiarism. I understand that
making a false declaration is a form of malpractice.

Student’s signature

Grading grid
P1 P2 P3 M1 M2 M3 D1 D2
 Summative Feedback:  Resubmission Feedback:

Grade: Assessor Signature: Date:

Internal Verifier’s Comments:

Signature & Date:


ASSIGNMENT 1 BRIEF

Qualification BTEC Level 5 HND Diploma in Business

Unit number Unit 19: Data Structures and Algorithms

Assignment title Examine and specify ADT and DSA

Academic Year

Unit Tutor

Issue date Submission date

IV name and date

Submission Format:

Format: The submission is in the form of an individual written report and a presentation. This should
be written in a concise, formal business style using single spacing and font size 12. You are
required to make use of headings, paragraphs and subsections as appropriate, and all work
must be supported with research and referenced using the Harvard referencing system.
Please also provide a bibliography using the Harvard referencing system.
Submission Students are compulsory to submit the assignment in due date and in a way requested by
the Tutors. The form of submission will be a soft copy in PDF posted on corresponding
course of http://cms.greenwich.edu.vn/
Note: The Assignment must be your own work, and not copied by or from another student or from
books etc. If you use ideas, quotes or data (such as diagrams) from books, journals or other sources, you
must reference your sources, using the Harvard style. Make sure that you know how to reference
properly, and that understand the guidelines on plagiarism. If you do not, you definitely get fail

Assignment Brief and Guidance:

Scenario: You work as in-house software developer for Softnet Development Ltd, a software body-shop providing
network provisioning solutions. Your company is part of a collaborative service provisioning development project
and your company has won the contract to design and develop a middleware solution that will interface at the
front-end to multiple computer provisioning interfaces including SOAP, HTTP, JML and CLI, and the back-end
telecom provisioning network via CLI .

Your account manager has assigned you a special role that is to inform your team about designing and
implementing abstract data types. You have been asked to create a presentation for all collaborating partners on
how ADTs can be utilised to improve software design, development and testing. Further, you have been asked to
write an introductory report for distribution to all partners on how to specify abstract data types and algorithms in
a formal notation.

Tasks
Part 1

You will need to prepare a presentation on how to create a design specification for data structures, explaining the
valid operations that can be carried out on the structures using the example of:

1. A stack ADT, a concrete data structure for a First In First out (FIFO) queue.
2. Two sorting algorithms.
3. Two network shortest path algorithms.
Part 2

You will need to provide a formal written report that includes the following:

1. Explanation on how to specify an abstract data type using the example of software stack.
2. Explanation of the advantages of encapsulation and information hiding when using an ADT.
3. Discussion of imperative ADTs with regard to object orientation.

Learning Outcomes and Assessment Criteria

Pass Merit Distinction

LO1 Examine abstract data types, concrete data structures and


algorithms
D1 Analyse the operation, using
P1 Create a design M1 Illustrate, with an example, a illustrations, of two network
specification for data structures concrete data structure for a First shortest path algorithms,
explaining the valid operations In First out (FIFO) queue. providing an example of each.
that can be carried out on the M2 Compare the performance of
structures. two sorting algorithms.

P2 Determine the operations of


a memory stack and how it is
used to implement function
calls in a computer.
LO2 Specify abstract data types and algorithms in a formal notation

P3 Using an imperative M3 Examine the advantages of D2 Discuss the view that


definition, specify the abstract encapsulation and information imperative ADTs are a basis for
data type for a software stack. hiding when using an ADT. object orientation and, with
justification, state whether you
agree.
Table of Contents

Contents
LO1 Examine abstract data types, concrete data structures and algorithms ............................................... 1
P1 Create a design specification for data structures explaining the valid operations that can be
carried out on the structures........................................................................................................................... 19
A. Linked list ........................................................................................................................................................ 20
B. Selection sort .................................................................................................................................................. 20
C.Selection sort ............................................................................................................................................... 20
D.Bubble sort .................................................................................................................................................. 20
E.Linear search .................................................................................................................................................... 20
F. Binary search ................................................................................................................................................... 23
G Dijkstra algorithm ................................................................................................................................................ 24
H.Bellman-Ford algorithm ...................................................................................................................................... 28
P2 Determine the operations of a memory stack and how it is used to implement function calls in a
computer................................................................................................................................................................ 34
P3 Using an imperative definition, specify the abstract data type for a software stack. ....................... 34
Reference ................................................................................................................................................................ 34
ASSIGNMENT 1 ANSWERS
LO1 Examine abstract data types, concrete data structures and
algorithms
P1 Create a design specification for data structures explaining the
valid operations that can be carried out on the structures.
A. Linked list

A linked list is a way to store a collection of elements. Like an array these can be character or integers. Each
element in a linked list is stored in the form of a node.

Node:

A node is a collection of two sub-elements or parts. A data part that stores the element and a next part that
stores the link to the next node.

Linked List:

A linked list is formed when many such nodes are linked together to form a chain. Each node points to the next
node present in the order. The first node is always used as a reference to traverse the list and is called HEAD.
The last node points to NULL.

Page |1
Declaring a Linked list :

In C language, a linked list can be implemented using structure and pointers .

struct LinkedList{
int data;
struct LinkedList *next;
};

The above definition is used to create every node in the list. The data field stores the element and the next is a
pointer to store the address of the next node.

Noticed something unusual with next?

In place of a data type, struct LinkedList is written before next. That's because its a self-referencing pointer. It
means a pointer that points to whatever it is a part of. Here next is a part of a node and it will point to the next
node.

Creating a Node:

Let's define a data type of struct LinkedListto make code cleaner.

typedef struct LinkedList *node; //Define node as pointer of data type struct LinkedList

node createNode(){
node temp; // declare a node
temp = (node)malloc(sizeof(struct LinkedList)); // allocate memory using malloc()
temp->next = NULL;// make next point to NULL
return temp;//return the new node
}

typedef is used to define a data type in C.

malloc() is used to dynamically allocate a single block of memory in C, it is available in the header file stdlib.h.

sizeof() is used to determine size in bytes of an element in C. Here it is used to determine size of each node and
sent as a parameter to malloc.

The above code will create a node with data as value and next pointing to NULL.

Let's see how to add a node to the linked list:

node addNode(node head, int value){


node temp,p;// declare two nodes temp and p
temp = createNode();//createNode will return a new node with data = value and next pointing to NULL.
Page |2
temp->data = value; // add element's value to data part of node
if(head == NULL){
head = temp; //when linked list is empty
}
else{
p = head;//assign head to p
while(p->next != NULL){
p = p->next;//traverse the list until p is the last node.The last node always points to NULL.
}
p->next = temp;//Point the previous last node to the new node created.
}
return head;
}

Here the new node will always be added after the last node. This is known as inserting a node at the rear end.

Food for thought

This type of linked list is known as simple or singly linked list. A simple linked list can be traversed in only one
direction from head to the last node.

The last node is checked by the condition :

p->next = NULL;

Here -> is used to access next sub element of node p. NULL denotes no node exists after the current node , i.e.
its the end of the list.

Traversing the list:

The linked list can be traversed in a while loop by using the head node as a starting reference:

node p;
p = head;
while(p != NULL){
p = p->next;
}

B. Selection sort

The Selection sort algorithm is based on the idea of finding the minimum or maximum element in an unsorted
array and then putting it in its correct position in a sorted array.

Assume that the array $$A=[7,5,4,2]$$ needs to be sorted in ascending order.


Page |3
The minimum element in the array i.e. $$2$$ is searched for and then swapped with the element that is
currently located at the first position, i.e. $$7$$. Now the minimum element in the remaining unsorted array is
searched for and put in the second position, and so on.

Let’s take a look at the implementation.

void selection_sort (int A[ ], int n) {


// temporary variable to store the position of minimum element

int minimum;
// reduces the effective size of the array by one in each iteration.

for(int i = 0; i < n-1 ; i++) {

// assuming the first element to be the minimum of the unsorted array .


minimum = i ;

// gives the effective size of the unsorted array .

for(int j = i+1; j < n ; j++ ) {


if(A[ j ] < A[ minimum ]) { //finds the minimum element
minimum = j ;
}
}
// putting minimum element on its proper position.
swap ( A[ minimum ], A[ i ]) ;
}
}

At $$i^{th}$$ iteration, elements from position $$0$$ to $$i-1$$ will be sorted.

Page |4
Time Complexity:

To find the minimum element from the array of $$N$$ elements, $$N-1$$ comparisons are required. After
putting the minimum element in its proper position, the size of an unsorted array reduces to $$N-1$$ and then
$$N-2$$ comparisons are required to find the minimum in the unsorted array.

Therefore $$(N-1)$$ + $$(N-2 )$$ + $$.......$$ + $$1$$ = $$( N \cdot (N-1) ) / 2$$ comparisons and $$N$$ swaps
result in the overall complexity of $$O( N^2 )$$.

C. Bubble sort

Sorting Algorithms are concepts that every competitive programmer must know. Sorting algorithms can be
used for collections of numbers, strings, characters, or a structure of any of these types.

Bubble sort is based on the idea of repeatedly comparing pairs of adjacent elements and then swapping their
positions if they exist in the wrong order.

Assume that $$A [ ]$$ is an unsorted array of $$n$$ elements. This array needs to be sorted in ascending order.
The pseudo code is as follows:

void bubble_sort( int A[ ], int n ) {


int temp;
for(int k = 0; k< n-1; k++) {
Page |5
// (n-k-1) is for ignoring comparisons of elements which have already been compared in earlier iterations

for(int i = 0; i < n-k-1; i++) {


if(A[ i ] > A[ i+1] ) {
// here swapping of positions is being done.
temp = A[ i ];
A[ i ] = A[ i+1 ];
A[ i + 1] = temp;
}
}
}
}

Lets try to understand the pseudo code with an example: A [ ] = { 7, 4, 5, 2}

In step 1, $$7$$ is compared with $$4$$. Since $$7 \gt 4$$, $$7$$ is moved ahead of $$4$$. Since all the other
elements are of a lesser value than $$7$$, $$7$$ is moved to the end of the array.

Now the array is $$A[]=\{4, 5, 2, 7\}$$.

In step 2, $$4$$ is compared with $$5$$. Since $$5 \gt 4$$ and both $$4$$ and $$5$$ are in ascending order,
these elements are not swapped. However, when $$5$$ is compared with $$2$$, $$5 \gt 2$$ and these
elements are in descending order. Therefore, $$5$$ and $$2$$ are swapped.

Now the array is $$A[]=\{4, 2, 5, 7\}$$.

In step 3, the element $$4$$ is compared with $$2$$. Since $$4 \gt 2$$ and the elements are in descending
order, $$4$$ and $$2$$ are swapped.

Page |6
The sorted array is $$A[]=\{2, 4, 5, 7\}$$.

Complexity:
The complexity of bubble sort is $$O(n^2)$$ in both worst and average cases, because the entire array needs to
be iterated for every element.

D. Linear Search

Linear search is used on a collections of items. It relies on the technique of traversing a list from start to end by
exploring properties of all the elements that are found on the way.

For example, consider an array of integers of size $$N$$. You should find and print the position of all the
elements with value $$x$$. Here, the linear search is based on the idea of matching each element from the
beginning of the list to the end of the list with the integer $$x$$, and then printing the position of the element
if the condition is `True'.

Implementation:

The pseudo code for this example is as follows :

for(start to end of array)


{
if (current_element equals to 5)
{
print (current_index);
}
}

For example, consider the following image:

If you want to determine the positions of the occurrence of the number $$7$$ in this array. To determine the
positions, every element in the array from start to end, i.e., from index $$1$$ to index $$10$$ will be compared
with number $$7$$, to check which element matches the number $$7$$.

Time Complexity:
Page |7
The time complexity of the linear search is $$O(N)$$ because each element in an array is compared only once.

E.Binary Search

Binary search is the most popular Search algorithm.It is efficient and also one of the most commonly used
techniques that is used to solve problems.

If all the names in the world are written down together in order and you want to search for the position of a
specific name, binary search will accomplish this in a maximum of $$35$$ iterations.

Binary search works only on a sorted set of elements. To use binary search on a collection, the collection must
first be sorted.

When binary search is used to perform operations on a sorted set, the number of iterations can always be
reduced on the basis of the value that is being searched.

Let us consider the following array:

By using linear search, the position of element 8 will be determined in the $$9^{th}$$ iteration.

Let's see how the number of iterations can be reduced by using binary search. Before we start the search, we
need to know the start and end of the range. Lets call them Low and High.

Low = 0
High = n-1

Now, compare the search value $$K$$ with the element located at the median of the lower and upper bounds.
If the value $$K$$ is greater, increase the lower bound, else decrease the upper bound.

Page |8
Referring to the image above, the lower bound is $$0$$ and the upper bound is $$9$$. The median of the
lower and upper bounds is (lower_bound + upper_bound) / 2 = 4. Here a[4] = 4. The value 4>2, which is the
value that you are searching for. Therefore, we do not need to conduct a search on any element beyond 4 as
the elements beyond it will obviously be greater than 2.

Therefore, we can always drop the upper bound of the array to the position of element 4. Now, we follow the
same procedure on the same array with the following values:

Low: 0
High: 3

Repeat this procedure recursively until Low > High. If at any iteration, we get $$a[mid]= key$$, we return value
of $$mid$$. This is the position of $$key$$ in the array. If $$key$$ is not present in the array, we return $$-1$$.

Implementation:

int binarySearch(int low,int high,int key)


{
while(low<=high)
{
int mid=(low+high)/2;
if(a[mid]<key)
{
low=mid+1;
}
else if(a[mid]>key)
{
high=mid-1;
}
else
Page |9
{
return mid;
}
}
return -1; //key not found
}

Time complexity

As we dispose off one part of the search case during every step of binary search, and perform the search
operation on the other half, this results in a worst case time complexity of $$O(log _{2} N) $$.

F.Dijkstra algorithm

Dijkstra's algorithm (or Dijkstra's Shortest Path First algorithm, SPF algorithm)[1] is an algorithm for finding
the shortest paths between nodes in a graph, which may represent, for example, road networks. It was
conceived by computer scientist Edsger W. Dijkstra in 1956 and published three years later.[2][3][4]
The algorithm exists in many variants. Dijkstra's original algorithm found the shortest path between two given
nodes,[4] but a more common variant fixes a single node as the "source" node and finds shortest paths from the
source to all other nodes in the graph, producing a shortest-path tree.
For a given source node in the graph, the algorithm finds the shortest path between that node and every
other.[5]:196–206 It can also be used for finding the shortest paths from a single node to a single destination node
by stopping the algorithm once the shortest path to the destination node has been determined. For example, if
the nodes of the graph represent cities and edge path costs represent driving distances between pairs of cities
connected by a direct road (for simplicity, ignore red lights, stop signs, toll roads and other obstructions),
Dijkstra's algorithm can be used to find the shortest route between one city and all other cities. A widely used
application of shortest path algorithm is network routing protocols, most notably IS-IS (Intermediate System to
Intermediate System) and Open Shortest Path First (OSPF). It is also employed as a subroutine in other
algorithms such as Johnson's.
The Dijkstra algorithm uses labels that are positive integers or real numbers, which are totally ordered. It can be
generalized to use any labels that are partially ordered, provided the subsequent labels (a subsequent label is
produced when traversing an edge) are monotonically non-decreasing. This generalization is called the generic
Dijkstra shortest-path algorithm.[6]
Dijkstra's algorithm uses a data structure for storing and querying partial solutions sorted by distance from the

start. The original algorithm uses a min-priority queue and runs in time (where is the number of
nodes). The idea of this algorithm is also given in Leyzorek et al. 1957. Fredman & Tarjan 1984 propose using

a Fibonacci heap min-priority queue to optimize the running time complexity to (where is the
number of edges). This is asymptotically the fastest known single-source shortest-path algorithm for
arbitrary directed graphs with unbounded non-negative weights. However, specialized cases (such as
bounded/integer weights, directed acyclic graphs etc.) can indeed be improved further as detailed
in Specialized variants.
In some fields, artificial intelligence in particular, Dijkstra's algorithm or a variant of it is known as uniform cost
search and formulated as an instance of the more general idea of best-first search.[7]

P a g e | 10
Let the node at which we are starting be called the initial node. Let the distance of node Y be the distance from
the initial node to Y. Dijkstra's algorithm will assign some initial distance values and will try to improve them
step by step.

1. Mark all nodes unvisited. Create a set of all the unvisited nodes called the unvisited set.
2. Assign to every node a tentative distance value: set it to zero for our initial node and to infinity for all
other nodes. Set the initial node as current.[13]
3. For the current node, consider all of its unvisited neighbours and calculate their tentative distances
through the current node. Compare the newly calculated tentative distance to the current assigned
value and assign the smaller one. For example, if the current node A is marked with a distance of 6, and
the edge connecting it with a neighbour B has length 2, then the distance to B through A will be 6 + 2 =
8. If B was previously marked with a distance greater than 8 then change it to 8. Otherwise, the current
value will be kept.
4. When we are done considering all of the unvisited neighbours of the current node, mark the current
node as visited and remove it from the unvisited set. A visited node will never be checked again.
5. If the destination node has been marked visited (when planning a route between two specific nodes) or
if the smallest tentative distance among the nodes in the unvisited set is infinity (when planning a
complete traversal; occurs when there is no connection between the initial node and remaining
unvisited nodes), then stop. The algorithm has finished.
6. Otherwise, select the unvisited node that is marked with the smallest tentative distance, set it as the
new "current node", and go back to step 3.
When planning a route, it is actually not necessary to wait until the destination node is "visited" as above: the
algorithm can stop once the destination node has the smallest tentative distance among all "unvisited" nodes
(and thus could be selected as the next "current").

G.Bellman-Ford algorithm

The Bellman–Ford algorithm is an algorithm that computes shortest paths from a single source vertex to all of
the other vertices in a weighted digraph.[1] It is slower than Dijkstra's algorithm for the same problem, but more
versatile, as it is capable of handling graphs in which some of the edge weights are negative numbers. The
algorithm was first proposed by Alfonso Shimbel (1955), but is instead named after Richard Bellman and Lester
Ford Jr., who published it in 1958 and 1956, respectively.[2] Edward F. Moore also published the same algorithm
in 1957, and for this reason it is also sometimes called the Bellman–Ford–Moore algorithm.[1]
Negative edge weights are found in various applications of graphs, hence the usefulness of this algorithm.[3] If a
graph contains a "negative cycle" (i.e. a cycle whose edges sum to a negative value) that is reachable from the
source, then there is no cheapest path: any path that has a point on the negative cycle can be made cheaper by
one more walk around the negative cycle. In such a case, the Bellman–Ford algorithm can detect and report the
negative cycle.[1][4]
Like Dijkstra's algorithm, Bellman–Ford proceeds by relaxation, in which approximations to the correct distance
are replaced by better ones until they eventually reach the solution. In both algorithms, the approximate
distance to each vertex is always an overestimate of the true distance, and is replaced by the minimum of its
old value and the length of a newly found path.

function BellmanFord(list vertices, list edges, vertex source)

P a g e | 11
::distance[],predecessor[]

// This implementation takes in a graph, represented as


// lists of vertices and edges, and fills two arrays
// (distance and predecessor) about the shortest path
// from the source to each vertex

// Step 1: initialize graph


for each vertex v in vertices:
distance[v] := inf // Initialize the distance to all vertices to infinity
predecessor[v] := null // And having a null predecessor

distance[source] := 0 // The distance from the source to itself is, of course, zero

// Step 2: relax edges repeatedly


for i from 1 to size(vertices)-1: //just |V|-1 repetitions; i is never referenced
for each edge (u, v) with weight w in edges:
if distance[u] + w < distance[v]:
distance[v] := distance[u] + w
predecessor[v] := u

// Step 3: check for negative-weight cycles


for each edge (u, v) with weight w in edges:
if distance[u] + w < distance[v]:
error "Graph contains a negative-weight cycle"

return distance[], predecessor[]

P2 Determine the operations of a memory stack and how it is used to


implement function calls in a computer.
A.Stack

Stacks are dynamic data structures that follow the Last In First Out (LIFO) principle. The last item to be inserted
into a stack is the first one to be deleted from it.

For example, you have a stack of trays on a table. The tray at the top of the stack is the first item to be moved if
you require a tray from that stack.

Inserting and deleting elements

Stacks have restrictions on the insertion and deletion of elements. Elements can be inserted or deleted only
from one end of the stack i.e. from the $$top$$. The element at the top is called the $$top$$ element. The
operations of inserting and deleting elements are called $$push()$$ and $$pop()$$ respectively.

When the top element of a stack is deleted, if the stack remains non-empty, then the element just below the
previous top element becomes the new top element of the stack.
P a g e | 12
For example, in the stack of trays, if you take the tray on the top and do not replace it, then the second tray
automatically becomes the top element (tray) of that stack.

Features of stacks

• Dynamic data structures


• Do not have a fixed size
• Do not consume a fixed amount of memory
• Size of stack changes with each $$push()$$ and $$pop()$$ operation. Each $$push()$$ and $$pop()$$
operation increases and decreases the size of the stack by $$1$$, respectively.

A stack can be visualized as follows:

Operations

push( x ): Insert element x at the top of a stack

void push (int stack[ ] , int x , int n) {


if ( top == n-1 ) { //If the top position is the last of position in a stack, this means that the stack is full
cout << “Stack is full.Overflow condition!” ;
}
else{
top = top +1 ; //Incrementing top position
stack[ top ] = x ; //Inserting element on incremented position
}
}

P a g e | 13
pop( ): Removes an element from the top of a stack

void pop (int stack[ ] ,int n )


{

if( isEmpty ( ) )
{
cout << “Stack is empty. Underflow condition! ” << endl ;
}
else
{
top = top - 1 ; //Decrementing top’s position will detach last element from stack
}
}

topElement ( ): Access the top element of a stack

int topElement ( )
{
return stack[ top ];
}

isEmpty ( ) : Check whether a stack is empty

bool isEmpty ( )
{
if ( top == -1 ) //Stack is empty
return true ;
else
return false;
}

size ( ): Determines the current size of a stack

int size ( )
{
return top + 1;
}

Implementation

#include <iostream>
using namespace std;
P a g e | 14
int top = -1; //Globally defining the value of top as the stack is empty

void push (int stack[ ] , int x , int n)


{
if ( top == n-1 ) //If the top position is the last of position of the stack, this means that the stack is full.
{
cout << "Stack is full.Overflow condition!" ;
}
else
{
top = top +1 ; //Incrementing the top position
stack[ top ] = x ; //Inserting an element on incremented position
}
}
bool isEmpty ( )
{
if ( top == -1 ) //Stack is empty
return true ;
else
return false;
}
void pop ( )
{

if( isEmpty ( ) )
{
cout << "Stack is empty. Underflow condition! " << endl ;
}
else
{
top = top - 1 ; //Decrementing top’s position will detach last element from stack
}
}
int size ( )
{
return top + 1;
}
int topElement (int stack[])
{
return stack[ top ];
}
//Let's implement these functions on the stack given above

int main( )
{
int stack[ 3 ];
// pushing element 5 in the stack .
push(stack , 5 , 3 ) ;

P a g e | 15
cout << "Current size of stack is " << size ( ) << endl ;

push(stack , 10 , 3);
push (stack , 24 , 3) ;

cout << "Current size of stack is " << size( ) << endl ;

//As the stack is full, further pushing will show an overflow condition.
push(stack , 12 , 3) ;

//Accessing the top element


cout << "The current top element in stack is " << topElement(stack) << endl;

//Removing all the elements from the stack


for(int i = 0 ; i < 3;i++ )
pop( );
cout << "Current size of stack is " << size( ) << endl ;

//As the stack is empty , further popping will show an underflow condition.
pop ( );

Output

• Current size of stack: 1


• Current size of stack: 3
• Current top element in stack: 24 (Stack is full. Overflow condition!)
• Current size of stack: 0 (Stack is empty. Underflow condition!)

Refer to the following image for more information about the operations performed in the code.

P a g e | 16
P a g e | 17
Application

Consider the balanced parentheses problem.

You have a bracket sequence made up of opening '(' and closing ')' parentheses. You must check if this bracket
sequence is balanced.

A bracket sequence is considered balanced if for every prefix of the sequence, the number of opening brackets
is greater than or equal to the number of closing brackets, and the total number of opening brackets is equal to
the number of closing brackets.

You can check this using stack. Let's see how.

You can maintain a stack where you store a parenthesis. Whenever, you come across an opening parenthesis,
$$push$$ it in the stack. However, whenever you come across a closing parenthesis, $$pop$$ a parenthesis
from the stack.

#include <iostream>
using namespace std;
int top;
void check (char str[ ], int n, char stack [ ])
{
for(int i = 0 ; i < n ; i++ )
{
if (str [ i ] == ‘(’)
{
top = top + 1;
stack[ top ] = ‘ ( ’;
}
if(str[ i ] == ‘)’ )
{
if(top == -1 )
{
top = top -1 ;
break ;
}
else
{
top = top -1 ;
}
}
}
if(top == -1)
cout << “String is balanced!” << endl;
else
cout << “String is unbalanced!” << endl ;
}

P a g e | 18
int main ( )
{
//balanced parenthesis string.
char str[ ] = { ‘(‘ , ‘a’ , ‘+’, ‘ ( ’, ‘b ’ , ‘-’ , ‘ c’ ,‘)’ , ‘ ) ’} ;

// unbalanced string .
char str1 [ ] = { ‘(’ , ‘(’ , ‘a’ , ‘ + ’ , ‘ b’ , ‘)’ } ;
char stack [ 15 ] ;
top = -1;
check (str , 9 , stack ); //Passing balanced string
top = -1 ;
check(str1 , 5 , stack) ; //Passing unbalanced string
return 0;

Output

String is balanced!
String is unbalanced!

B.Memory stack

The Stack

What is the stack? It's a special region of your computer's memory that stores temporary variables created by
each function (including the main() function). The stack is a "LIFO" (last in, first out) data structure, that is
managed and optimized by the CPU quite closely. Every time a function declares a new variable, it is "pushed"
onto the stack. Then every time a function exits, all of the variables pushed onto the stack by that function, are
freed (that is to say, they are deleted). Once a stack variable is freed, that region of memory becomes available
for other stack variables.

The advantage of using the stack to store variables, is that memory is managed for you. You don't have to
allocate memory by hand, or free it once you don't need it any more. What's more, because the CPU organizes
stack memory so efficiently, reading from and writing to stack variables is very fast.

A key to understanding the stack is the notion that when a function exits, all of its variables are popped off of
the stack (and hence lost forever). Thus stack variables are local in nature. This is related to a concept we saw
earlier known as variable scope, or local vs global variables. A common bug in C programming is attempting to
access a variable that was created on the stack inside some function, from a place in your program outside of
that function (i.e. after that function has exited).

Another feature of the stack to keep in mind, is that there is a limit (varies with OS) on the size of variables that
can be stored on the stack. This is not the case for variables allocated on the heap.

To summarize the stack:

P a g e | 19
• the stack grows and shrinks as functions push and pop local variables
• there is no need to manage the memory yourself, variables are allocated and freed automatically
• the stack has size limits
• stack variables only exist while the function that created them, is running

The Heap

The heap is a region of your computer's memory that is not managed automatically for you, and is not as tightly
managed by the CPU. It is a more free-floating region of memory (and is larger). To allocate memory on the
heap, you must use malloc() or calloc(), which are built-in C functions. Once you have allocated memory on the
heap, you are responsible for using free() to deallocate that memory once you don't need it any more. If you fail
to do this, your program will have what is known as a memory leak. That is, memory on the heap will still be set
aside (and won't be available to other processes). As we will see in the debugging section, there is a tool
called valgrind that can help you detect memory leaks.

Unlike the stack, the heap does not have size restrictions on variable size (apart from the obvious physical
limitations of your computer). Heap memory is slightly slower to be read from and written to, because one has
to use pointers to access memory on the heap. We will talk about pointers shortly.

Unlike the stack, variables created on the heap are accessible by any function, anywhere in your program. Heap
variables are essentially global in scope.

Stack vs Heap Pros and Cons

Stack

• very fast access


• don't have to explicitly de-allocate variables
• space is managed efficiently by CPU, memory will not become fragmented
• local variables only
• limit on stack size (OS-dependent)
• variables cannot be resized

Heap

• variables can be accessed globally


• no limit on memory size
• (relatively) slower access
• no guaranteed efficient use of space, memory may become fragmented over time as blocks of memory
are allocated, then freed
• you must manage memory (you're in charge of allocating and freeing variables)
• variables can be resized using realloc()

Examples

Here is a short program that creates its variables on the stack. It looks like the other programs we have seen so
far.

P a g e | 20
#include <stdio.h>

double multiplyByTwo (double input) {

double twice = input * 2.0;

return twice;

int main (int argc, char *argv[])

int age = 30;

double salary = 12345.67;

double myList[3] = {1.2, 2.3, 3.4};

printf("double your salary is %.3f\n", multiplyByTwo(salary));

return 0;

double your salary is 24691.340

On lines 10, 11 and 12 we declare variables: an int, a double, and an array of three doubles. These three
variables are pushed onto the stack as soon as the main() function allocates them. When the main() function
exits (and the program stops) these variables are popped off of the stack. Similarly, in the
function multiplyByTwo(), the twice variable, which is a double, is pushed onto the stack as soon as
the multiplyByTwo() function allocates it. As soon as the multiplyByTwo() function exits, the twice variable is
popped off of the stack, and is gone forever.

P a g e | 21
As a side note, there is a way to tell C to keep a stack variable around, even after its creator function exits, and
that is to use the static keyword when declaring the variable. A variable declared with the static keyword thus
becomes something like a global variable, but one that is only visible inside the function that created it. It's a
strange construction, one that you probably won't need except under very specific circumstances.

Here is another version of this program that allocates all of its variables on the heap instead of the stack:

#include <stdio.h>

#include <stdlib.h>

double *multiplyByTwo (double *input) {

double *twice = malloc(sizeof(double));

*twice = *input * 2.0;

return twice;

int main (int argc, char *argv[])

int *age = malloc(sizeof(int));

*age = 30;

double *salary = malloc(sizeof(double));

*salary = 12345.67;

double *myList = malloc(3 * sizeof(double));

myList[0] = 1.2;

myList[1] = 2.3;

myList[2] = 3.4;

P a g e | 22
double *twiceSalary = multiplyByTwo(salary);

printf("double your salary is %.3f\n", *twiceSalary);

free(age);

free(salary);

free(myList);

free(twiceSalary);

return 0;

As you can see, using malloc() to allocate memory on the heap and then using free() to deallocate it, is no big
deal, but is a bit cumbersome. The other thing to notice is that there are a bunch of star symbols * all over the
place now. What are those? The answer is, they are pointers. The malloc() (and calloc() and free()) functions
deal with pointers not actual values. We will talk more about pointers shortly. The bottom line though: pointers
are a special data type in C that store addresses in memory instead of storing actual values. Thus on line 5
above, the twice variable is not a double, but is a pointer to a double. It's an address in memory where
the double is stored.

When to use the Heap?

When should you use the heap, and when should you use the stack? If you need to allocate a large block of
memory (e.g. a large array, or a big struct), and you need to keep that variable around a long time (like a
global), then you should allocate it on the heap. If you are dealing with relatively small variables that only need
to persist as long as the function using them is alive, then you should use the stack, it's easier and faster. If you
need variables like arrays and structs that can change size dynamically (e.g. arrays that can grow or shrink as
needed) then you will likely need to allocate them on the heap, and use dynamic memory allocation functions
like malloc(), calloc(), realloc() and free() to manage that memory "by hand". We will talk about dynamically
allocated data structures after we talk about pointers.

P3 Using an imperative definition, specify the abstract data type for


a software stack.
P a g e | 23
Pseudocode

Push(S,x)
if Stack-Full(S)
then error "overflow"
else top(S) = top(S) + 1
S[top(S)] = x

Pop(S)
if Stack-Empty(S)
then error "underflow"
else top(S) = top(S) - 1
return S[top(S) + 1]

Stack-Empty(S)
if top(S) = 0
then return True
else return False

Stack-Full(S)
if top(S) = length(S)
then return True
else return False
Stack Java

Below is the syntax highlighted version of Stack.java from §4.3 Stacks and Queues.

/******************************************************************************
* Compilation: javac Stack.java
* Execution: java Stack < input.txt
* Data files: https://introcs.cs.princeton.edu/java/43stack/tobe.txt
*
* A generic stack, implemented using a linked list. Each stack
* element is of type Item.
*
* % more tobe.txt
* to be or not to - be - - that - - - is
*
* % java Stack < tobe.txt
* to be not that or be (2 left on stack)
*
******************************************************************************/

P a g e | 24
import java.util.Iterator;
import java.util.NoSuchElementException;

/**
* The {@code Stack} class represents a last-in-first-out (LIFO) stack of generic items.
* It supports the usual <em>push</em> and <em>pop</em> operations, along with methods
* for peeking at the top item, testing if the stack is empty, getting the number of
* items in the stack, and iterating over the items in LIFO order.
* <p>
* This implementation uses a singly-linked list with a nested class for
* linked-list nodes.
* The <em>push</em>, <em>pop</em>, <em>peek</em>, <em>size</em>, and <em>is-empty</em>
* operations all take constant time in the worst case.
* <p>
* For additional documentation,
* see <a href="https://introcs.cs.princeton.edu/43stack">Section 4.3</a> of
* <i>Computer Science: An Interdisciplinary Approach</i>
* by Robert Sedgewick and Kevin Wayne.
*
* @author Robert Sedgewick
* @author Kevin Wayne
*
* @param <Item> the generic type of an item in this stack
*/
public class Stack<Item> implements Iterable<Item> {
private int n; // size of the stack
private Node first; // top of stack

// helper linked list class


private class Node {
private Item item;
private Node next;
}

/**
* Initializes an empty stack.
*/
public Stack() {
first = null;
n = 0;
}

/**
* Returns true if this stack is empty.
*
* @return true if this stack is empty; false otherwise
*/
public boolean isEmpty() {
P a g e | 25
return first == null;
}

/**
* Returns the number of items in this stack.
*
* @return the number of items in this stack
*/
public int size() {
return n;
}

/**
* Adds the item to this stack.
*
* @param item the item to add
*/
public void push(Item item) {
Node oldfirst = first;
first = new Node();
first.item = item;
first.next = oldfirst;
n++;
}

/**
* Removes and returns the item most recently added to this stack.
*
* @return the item most recently added
* @throws NoSuchElementException if this stack is empty
*/
public Item pop() {
if (isEmpty()) throw new NoSuchElementException("Stack underflow");
Item item = first.item; // save item to return
first = first.next; // delete first node
n--;
return item; // return the saved item
}

/**
* Returns (but does not remove) the item most recently added to this stack.
*
* @return the item most recently added to this stack
* @throws NoSuchElementException if this stack is empty
*/
public Item peek() {
if (isEmpty()) throw new NoSuchElementException("Stack underflow");
return first.item;
P a g e | 26
}

/**
* Returns a string representation of this stack.
*
* @return the sequence of items in this stack in LIFO order, separated by spaces
*/
public String toString() {
StringBuilder s = new StringBuilder();
for (Item item : this) {
s.append(item);
s.append(' ');
}
return s.toString();
}

/**
* Returns an iterator to this stack that iterates through the items in LIFO order.
*
* @return an iterator to this stack that iterates through the items in LIFO order
*/
public Iterator<Item> iterator() { return new ListIterator(); }

// an iterator, doesn't implement remove() since it's optional


private class ListIterator implements Iterator<Item> {
private Node current = first;
public boolean hasNext() { return current != null; }
public void remove() { throw new UnsupportedOperationException(); }

public Item next() {


if (!hasNext()) throw new NoSuchElementException();
Item item = current.item;
current = current.next;
return item;
}
}

/**
* Unit tests the {@code Stack} data type.
*
* @param args the command-line arguments
*/
public static void main(String[] args) {
Stack<String> stack = new Stack<String>();
while (!StdIn.isEmpty()) {
String item = StdIn.readString();
if (!item.equals("-")) stack.push(item);
P a g e | 27
else if (!stack.isEmpty()) StdOut.print(stack.pop() + " ");
}
StdOut.println("(" + stack.size() + " left on stack)");
}
}

An Example Stack Operations

Assume we have a stack of size 3 which holds integers between -100 and 100. Here is a series of operations,
and the results.

Operations Stack contents Return


create ()
Push(55) (55) 1
push(-7) (-7,55) 1
push(16) (16,-7,55) 1
pop (-7,55) 16
push(-8) (-8,-7,55) 1
push(23) (-8,-7,55) 0
pop (-7,55) -8
pop (55) -7
pop () 55
pop () 101

stack push() and pop() in C++ STL

P a g e | 28
Stacks are a type of container adaptors with LIFO(Last In First Out) type of working, where a new element is
added at one end and (top) an element is removed from that end only.
stack::push()
push() function is used to insert an element at the top of the stack. The element is added to the stack container
and the size of the stack is increased by 1.
Syntax :

stackname.push(value)
Parameters :
The value of the element to be inserted is passed as the parameter.
Result :
Adds an element of value same as that of
the parameter passed at the top of the stack.
Examples:
Input : mystack
mystack.push(6);
Output : 6

Input : mystack
mystack.push(0);
mystack.push(1);
Output : 0, 1
Errors and Exceptions
1. Shows error if the value passed doesn’t match the stack type.
2. Shows no exception throw guarantee if the parameter doesn’t throw any exception.

// CPP program to illustrate

// Implementation of push() function

#include <iostream>

#include <stack>

using namespace std;

int main()

P a g e | 29
// Empty stack

stack<int> mystack;

mystack.push(0);

mystack.push(1);

mystack.push(2);

// Printing content of stack

while (!mystack.empty()) {

cout << ' ' << mystack.top();

mystack.pop();

Output:
210
Note that output is printed on the basis of LIFO property
stack::pop()
pop() function is used to remove an element from the top of the stack(newest element in the stack). The
element is removed to the stack container and the size of the stack is decreased by 1.
Syntax :
stackname.pop()
Parameters :
No parameters are passed.
Result :
Removes the newest element in the stack
or basically the top element.
Examples:
Input : mystack = 0, 1, 2
mystack.pop();
Output : 0, 1

Input : mystack = 0, 1, 2, 3, 4, 5
mystack.pop();
Output : 0, 1, 2, 3, 4
Errors and Exceptions
P a g e | 30
1. Shows error if a parameter is passed.
2. Shows no exception throw guarantee.

// CPP program to illustrate

// Implementation of pop() function

#include <iostream>

#include <stack>

using namespace std;

int main()

stack<int> mystack;

mystack.push(1);

mystack.push(2);

mystack.push(3);

mystack.push(4);

// Stack becomes 1, 2, 3, 4

mystack.pop();

mystack.pop();

// Stack becomes 1, 2

while (!mystack.empty()) {

cout << ' ' << mystack.top();

mystack.pop();

Output:
21
Note that output is printed on the basis of LIFO property

P a g e | 31
Application :
Given a number of integers, add them to the stack and find the size of the stack without using size function.
Input : 5, 13, 0, 9, 4
Output: 5
Algorithm
1. Push the given elements to the stack container one by one.
2. Keep popping the elements of stack until it becomes empty, and increment the counter variable.
3. Print the counter variable.

// CPP program to illustrate

// Application of push() and pop() function

#include <iostream>

#include <stack>

using namespace std;

int main()

int c = 0;

// Empty stack

stack<int> mystack;

mystack.push(5);

mystack.push(13);

mystack.push(0);

mystack.push(9);

mystack.push(4);

// stack becomes 5, 13, 0, 9, 4

// Counting number of elements in queue

while (!mystack.empty()) {

mystack.pop();

c++;

P a g e | 32
cout << c;

P a g e | 33
Reference
1.Wikipedia.org (2019) Shortest path problem [online] Available at :
https://en.wikipedia.org/wiki/Shortest_path_problem [Accessed 14/12/2019]

2. geeksforgeeks.org (2019) stack push() and pop() in C++ STL [online] Available at :
https://www.geeksforgeeks.org/stack-push-and-pop-in-c-stl/ [Accessed 14/12/2019]

3. hackerearth.com (2019) Dijkstra's Algorithm [online] Available at :


https://www.hackerearth.com/practice/notes/dijkstras-
algorithm/?utm_source=header&utm_medium=search&utm_campaign=he-search [Accessed
14/12/2019]

4. en.wikibooks.org (2019) Data Structure Stack and Queues [online] Available at :


https://en.wikibooks.org/wiki/Data_Structures/Stacks_and_Queues [Accessed 14/12/2019]

5. freecodecamp.org (2019) Data Structures 101: Stacks


Available at : https://www.freecodecamp.org/news/data-structures-101-stacks-696b3282980/
[Accessed 14/12/2019]

P a g e | 34

You might also like