Assignment 2 Front Sheet: Qualification BTEC Level 5 HND Diploma in Computing
Assignment 2 Front Sheet: Qualification BTEC Level 5 HND Diploma in Computing
Unit number and title Unit 19: Data Structures and Algorithms
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
P4 P5 P6 P7 M4 M5 D3 D4
Page. 1
Page. 2
Summative Feedback: Resubmission Feedback:
IV Signature:
Page. 3
Table of Contents
Introduction........................................................................................................................................ 8
1. Design and implementation of Stack ADT and Queue ADT (P4) ........................................................ 9
2. Application ................................................................................................................................... 16
Page. 4
5.1 Time efficiency...................................................................................................................................... 48
Conclusion ........................................................................................................................................ 53
References ........................................................................................................................................ 54
Page. 5
Table of Figures
Figure 1. Stack Data Structure ........................................................................................................................ 9
Figure 2. Stack push operation ..................................................................................................................... 10
Figure 3. Stack pop operation ...................................................................................................................... 10
Figure 4. Stack top operation ....................................................................................................................... 10
Figure 5. Queue ............................................................................................................................................ 11
Figure 6. enqueue ......................................................................................................................................... 12
Figure 7. dequeue ......................................................................................................................................... 12
Figure 8. Stack class ...................................................................................................................................... 14
Figure 9. Queue Class ................................................................................................................................... 15
Figure 10. Main program .............................................................................................................................. 18
Figure 11. Linked List .................................................................................................................................... 19
Figure 12. Doubly Linked List ........................................................................................................................ 20
Figure 13. Reverse Queue Function ............................................................................................................. 21
Figure 14. Move items from a Queue to a Stack .......................................................................................... 21
Figure 15. Dequeue process ......................................................................................................................... 22
Figure 16. Stack after dequeued .................................................................................................................. 22
Figure 17. Reverse a queue by moving data from FIFO -> LIFO -> FIFO....................................................... 23
Figure 18. A Stack is created for storing elements ....................................................................................... 24
Figure 19. De-queue all elements form the Queue and Push them to the Stack ........................................ 24
Figure 20. Pop all items from Stack and Push them back into the Queue ................................................... 24
Figure 21. Popping out items from Stacks and Push them to temporary stacks ......................................... 26
Figure 22. Pop out remaining elements and push them to the temporary stack ........................................ 26
Figure 23. Push elements back to their original Stack ................................................................................. 27
Figure 24. Checking for length and top value of two stacks. ....................................................................... 28
Figure 25. Initiate two Blank Stacks (Temporary Stacks) ............................................................................. 28
Figure 26. Checking elements in two Stacks ................................................................................................ 28
Figure 27. Push back elements to their original stacks ................................................................................ 29
Figure 28. Theta Notation Expression .......................................................................................................... 45
Page. 6
Figure 29. Theta Notation factors ................................................................................................................ 45
Figure 30. Big-O Notation Expression........................................................................................................... 46
Figure 31. Big-O Notation Factors ................................................................................................................ 46
Figure 32. Omega Notation Expression ........................................................................................................ 47
Figure 33. Omega Notation Factors ............................................................................................................. 47
Page. 7
Introduction
As a developer, the author has been requested for design and implement the two data structures that
were briefly described in the previous assignment, Stack and Queue. Two data structures should
demonstrate some important operations of these data structures.
• Write a function to reverse the order of a queue using a stack. Include proper annotations in your
solution. What is the worst-case run time complexity of this function if there are n elements in the
queue? Explain.
• Write a function that takes two stacks and returns true if the stacks contain the same elements in
the same order. After the operations, both stacks must remain in the original order. You are
allowed to use additional stacks or queues as needed, but no other data structures. What is the
worst-case run time complexity of your code if each stack contains n elements? Explain.
In this document, the author is going to show the implementation and explanation about the development
process of the project.
Page. 8
1. Design and implementation of Stack ADT and Queue ADT (P4)
1.1. Stack ADT
A stack is a container of objects that are inserted and removed according to the last-in first-out (LIFO)
principle. In the pushdown stacks only two operations are allowed: push the item into the stack, and pop
the item out of the stack. A stack is a limited access data structure - elements can be added and removed
from the stack only at the top. push adds an item to the top of the stack, pop removes the item from the
top. A helpful analogy is to think of a stack of books; you can remove only the top book, also you can add
a new book on the top. (S.Adamchik, 2009)
In simplified, stack is a linear data structure in a one-way order. The order can be in LIFO or FILO (however,
they seem to be similar).
Stack is a concept which is used mainly in data structures. It comes along with array concept for data
inserting, modifying and recovering.
Page. 9
Push (Item i) : Boolean
Push operation is used for inputting an item to the Stack. The operation
will add or insert an item to the Stack whether the Stack is still empty.
Once the Stack is full (full of memory), the operation will notice that the
Stack is overflow so cannot add anymore.
Pop () : Boolean
Pop operation is used for taking (or removing) an item from the Stack.
Pop operation will be performed no matter the stack is full or not, but
will not perform when the Stack is empty.
Once the Stack is empty, it will notice that the Stack has no item in it.
Top operation is about taking the topmost item from the Stack.
There’re many applications of Stack in real life. The simplest application of Stack is for reversing a word by
putting it in a Stack.
Page. 10
1.2. Queue Data Structure
A queue is a Data Structure that’s similar to the ticket queue in fast-food store where the first entering
person will be the first one who get the goods.
Queue follows FIFO rule – item goes in first will be firstly processed.
Figure 5. Queue
Page. 11
Enqueue Operation
Figure 6. enqueue
Dequeue Operation
Figure 7. dequeue
Page. 12
1.3. Code Implementation
1. package com.nstudio.assignment_2;
2.
3. public class Stack<T> {
4. private class Node {
5. T data;
6. Node prev;
7.
8. Node(T data) {
9. this.data = data;
10. }
11. }
12.
13. private int maxLen;
14. private int curLen;
15. private Node top;
16.
17. public Stack(int maxLen) {
18. this.maxLen = maxLen;
19. this.curLen = 0;
20. }
21.
22. public boolean push(T data) {
23. if (curLen < maxLen) {
24. curLen ++;
25. Node newNode = new Node(data);
26. if (top != null) newNode.prev = top;
27. top = newNode;
28. return true;
29. } else {
30. return false;
31. }
32. }
33.
34. public T pop() {
35. Node r = top;
36. top = top.prev;
37. curLen--;
38. return r.data;
39. }
40.
41. public T top() {
42. return top.data;
43. }
44.
45. public int countItems() {
46. return curLen;
47. }
48.
49. public boolean isEmpty() {
50. return top == null;
51. }
52.
53. public void printAllNodes() {
54. Node curNode = top;
55. while (curNode != null) {
Page. 13
56. System.out.print(curNode.data + " -> ");
57. curNode = curNode.prev;
58. }
59. System.out.println();
60. }
61. }
1. package com.nstudio.assignment_2;
2.
3. public class Queue<T> {
4. private class Node {
5. T data;
6. Node next;
7. Node prev;
8. Node(T data) {
9. this.data = data;
10. next = null;
11. prev = null;
12. }
13. }
14.
15. Node head;
16. Node tail;
17. int maxLen;
18. int curLen;
19. public Queue (int maxLen) {
20. this.head = null;
21. this.tail = null;
22. this.maxLen = maxLen;
23. this.curLen = 0;
24. }
25. public boolean enQueue(T data) {
26. if (curLen < maxLen) {
27. Node newNode = new Node(data);
28. if (head == null || tail == null) {
29. head = tail = newNode;
30. head.next = tail;
31. tail.prev = head;
32. head.prev = null;
33. tail.next = null;
34. } else {
35. newNode.prev = tail;
36. tail.next = newNode;
37. tail = newNode;
38. }
39. curLen ++;
40. return true;
41. } else
42. return false;
43. }
44. public T deQueue() {
45. if (hasItem()) {
46. Node r = head;
47. head = head.next;
48. if (head != null) head.prev = null;
Page. 14
49. curLen --;
50. return r.data;
51. } else {
52. return null;
53. }
54. }
55. public boolean hasItem() {
56. return head != null && tail != null;
57. }
58. public int getLength() { return curLen; }
59.
60. public void printAllNodes() {
61. Node curNode = head;
62. while (curNode != null) {
63. System.out.print(" <- " + curNode.data);
64. curNode = curNode.next;
65. }
66. System.out.println();
67. }
68. }
Page. 15
2. Application
2.1. Introduction
Problem 1: Write a function to reverse the order of a queue using a stack. Include proper annotations in
your solution. What is the worst-case run time complexity of this function if there are n elements in the
queue? Explain.
Problem 2: Write a function that takes two stacks and returns true if the stacks contain the same elements
in the same order. After the operations, both stacks must remain in the original order. You are allowed to
use additional stacks or queues as needed, but no other data structures. What is the worst-case run time
complexity of your code if each stack contains n elements? Explain.
2.2. Implementation
Code implement in Java:
Page. 16
1. package com.nstudio.assignment_2;
2.
3. public class Main {
4. static int dataLength = 20;
5.
6. public static void main(String[] args) {
7.
8. Queue<Integer> myQueue = new Queue<Integer>(dataLength);
9.
10. myQueue.enQueue(1);
11. myQueue.enQueue(2);
12. myQueue.enQueue(3);
13. myQueue.enQueue(4);
14. System.out.println("Current Queue:");
15. myQueue.printAllNodes();
16. System.out.println("Reversed Queue is:");
17. reverseQueue(myQueue);
18. myQueue.printAllNodes();
19.
20. Stack<Integer> s = new Stack<Integer>(20);
21. s.push(2);
22. s.push(3);
23. s.push(4);
24.
25. System.out.println("Stack s:");
26. s.printAllNodes();
27.
28. Stack<Integer> s1 = new Stack<Integer>(20);
29. s1.push(2);
30. s1.push(3);
31. s1.push(4);
32. s1.push(5);
33. s1.push(6);
34.
35. System.out.println("Stack s1:");
36. s1.printAllNodes();
37.
38. System.out.println(compareStacks(s, s1) ? "stack is equal" : "stack is not equal");
39. s.printAllNodes();
40. s1.printAllNodes();
41.
42. }
43.
44. public static void reverseQueue(Queue<Integer> myQueue) {
45. Stack<Integer> myStack = new Stack<Integer>(myQueue.getLength());
46. while (myQueue.hasItem()) {
47. int item = myQueue.deQueue();
48. myStack.push(item);
49. }
50. while (!myStack.isEmpty()) {
51. int item = myStack.pop();
52. myQueue.enQueue(item);
53. }
54. }
55.
56. public static boolean compareStacks(Stack<Integer> stack1, Stack<Integer> stack2) {
57. if (stack1.countItems() != stack2.countItems()) return false;
58. if (!stack1.top().equals(stack2.top())) return false;
59. Stack<Integer> tempStack1 = new Stack<>(dataLength);
60. Stack<Integer> tempStack2 = new Stack<>(dataLength);
61.
Page. 17
62. boolean isEqual = true;
63. while (!stack1.isEmpty()) {
64. int poppedValue1 = stack1.pop();
65. int poppedValue2 = stack2.pop();
66. if (poppedValue1 == poppedValue2) {
67. tempStack1.push(poppedValue1);
68. tempStack2.push(poppedValue2);
69. } else {
70. isEqual = false;
71. break;
72. }
73. }
74.
75. while (!stack1.isEmpty()) tempStack1.push(stack1.pop());
76. while (!stack2.isEmpty()) tempStack2.push(stack2.pop());
77.
78. while (!tempStack1.isEmpty()) stack1.push(tempStack1.pop());
79. while (!tempStack2.isEmpty()) stack2.push(tempStack2.pop());
80. return isEqual;
81. }
82. }
Page. 18
2.3. Explanation
By applying the above source code, the author has successfully achieved the requirements of the topic in
this document.
Not same as previously discussed in “Assignment 1 Document”, instead of saving head node, for better
performance, the ADT will save the last node of the Stack (top node). And each node saves the link to the
previous node of that node.
This will makes Pop Operation and Push Operation have Time Complexity O (1) since the program only
has to append a node and removing last node of the Stack instead of looping through the Stack from the
first node to last node as in previous document.
There’s no more Space used for temporary value, so the Space Complexity is O (1).
Page. 19
2.3.1.2. Queue ADT
Same as Stack ADT, Queue ADT implemented in this project also using Linked List to store data. However,
different from Stack, the Queue ADT uses Doubly Linked List.
Doubly Linked List has all nodes save both pointer of previous node and next node.
By using Doubly Linked List, the program can easily navigate to the first and last item (in the queue) without
looping through data structure’s length to find the needed position.
The Queue ADT initialized with front and rear node saved as default. This preserves that Time Complexity
of enqueue and dequeue Operations are O (1).
This implementation also doesn’t use more space to store temporary value, so the Space Complexity also
is O (1).
Page. 20
2.3.2. Problem 1: Reverse a Queue using a Stack
As previously discussed, the Queue follows FIFO while Stack is LIFO structured. In this Source Code, a
function has been created to solve the problem:
2.3.3.1. Algorithm
The main working mechanism used here is to de-queue all items from the Queue and push them all to the
Stack. Since the working orientation of two Data Structure are opposite, so when the program enqueue
them again into the queue, all items will be in a reversed order.
As in this example, a Queue which is having value of “H E L L O” (in FIFO), will sequency take out items in
this order:
Page. 21
Figure 15. Dequeue process
So, the taken-out string will be the same as Queue displaying orders: H E L L O.
After dequeued, the Character is pushed into the stack right away in dequeue order (HELLO). So the Stack
now is:
Page. 22
This order is also the orientation when Stack items are popped out. The FIFO keeps the orientation of the
item inserted into the data structure while LIFO reverses it. So, the final data of the Queue when the Stack
popped and enqueued all items:
Figure 17. Reverse a queue by moving data from FIFO -> LIFO -> FIFO
By moving data from FIFO to LIFO then back to FIFO, the author can achieve reversing elements in a queue
of data.
Page. 23
2.3.3.2. Code Explanation
Following the algorithm previously discussed, the program created a Stack for storing Queue elements
after popped them all.
The length of the Stack is same as the Queue length (actually, the program is setting max Length of the
queue. However, for strict purpose, the max length will have same length as queue length).
Then, the program will take out all elements in the queue and push them to the Stack.
1. while (myQueue.hasItem()) {
2. int item = myQueue.deQueue();
3. myStack.push(item);
4. }
Figure 19. De-queue all elements form the Queue and Push them to the Stack
In this part, the order of items has been reversed. First element previously stored in the Queue now is the
last item stored (and will be taken out) in the Stack.
Then, the program will pop out all items in the Stack and enqueue back to the Queue.
1. while (!myStack.isEmpty()) {
2. int item = myStack.pop();
3. myQueue.enQueue(item);
4. }
Figure 20. Pop all items from Stack and Push them back into the Queue
Since FIFO doesn’t affect on the order of elements, the data in the Queue is now same order as the way
they are stored in the Stack.
In this step, the Queue has been successfully reversed using a Stack.
Page. 24
2.3.3.3. Time Complexity & Space Complexity
It can be separated the algorithm into 2 steps:
- Enqueue out all items in the queue and push into the stack.
- Pop all the items form the stack and enqueue back into the queue.
The dequeue and push process takes n steps, while the pop process and enqueue also take n steps more.
Since the program uses a stack more to store value, the Space Complexity of the algorithm is O (n).
Page. 25
2.3.3. Problem 2: Compare Two Stacks Preserve Data
2.3.3.1. Algorithm
The method used is about getting out all data from the two Stacks and push them into two Temporary
Stacks. In popping out process, the program will also compare the value of popped out value. When found
a popped-out pair that is not having same value, it will return false and pop and push remaining elements
in two Stacks into those Temporary Stacks.
Figure 21. Popping out items from Stacks and Push them to temporary stacks
Figure 22. Pop out remaining elements and push them to the temporary stack
Page. 26
Finally, putting elements back to the two original Stacks.
In case the program cannot find any elements that’s not equal (all elements are equal), then return true.
For better performance, there’re something that will make the algorithm performs faster:
If the two stacks fail one of two requirements (or both), the algorithm stops since two stacks must have
same elements and size.
Page. 27
2.3.3.2. Code Explanation
Firstly, the program will check for the length of the two stacks. If they don’t have same length, the
algorithm will stop with false value returned.
Figure 24. Checking for length and top value of two stacks.
Otherwise, the program will continue and check for the top element of two stack.
Next, the program initiates two Blank Stacks (temporary stacks – same size as the input stacks).
Since the program cannot stop right away when it found an un-equal pair of values that taken from Stacks
(it will break the data and not preserve the two stacks as its original state), a variable is required to record
the final return of the function: boolean isEqual = true;
Because of the length of two stacks are equal (previously checked in the first step), the loop through the
data only has to traverse while one of two stacks are not blank. Every step, the program pops out value
from the two stacks and push them into temporary stacks before checking for the value. By pushing value
to temporary stacks before continue, the algorithm avoids breaking data or missing records.
Next, in case two value is not the same, the return variable will be set to false and the loop will be broken.
1. while (!stack1.isEmpty()) {
2. int poppedValue1 = stack1.pop();
3. int poppedValue2 = stack2.pop();
4. tempStack1.push(poppedValue1);
5. tempStack2.push(poppedValue2);
6. if (poppedValue1 != poppedValue2) isEqual = false;
7. }
After have done the result, the program needs to put back the state of two stacks to their original. So, all
elements in temporary stacks will be push back to the original stacks:
Page. 28
1. while (!tempStack1.isEmpty()) {
2. stack1.push(tempStack1.pop());
3. stack2.push(tempStack2.pop());
4. }
And finally, the program only has to return the value: return isEqual;
- Popping out all items from two stacks and push them to the temporary stacks.
- Push all items from the temporary stacks back to the original stacks.
For Space Complexity, two more stacks are used to store values.
Page. 29
3. Error Handling
3.1. Testing Plan
Firstly, initialize a Stack (MyStack) with max size of 3 and a Queue (MyQueue) with max size of 3.
Input Output
Test result:
Test result:
MyStack =
Push Data Validation [3, 4, 5] False False Pass
3 Push (6)
Test code:
Page. 30
Test result:
MyStack =
Pop Normal 5 5 Pass
[3, 4, 5]
Test code:
Test result:
MyStack =
Pop Normal 3 3 Pass
[3]
Test code:
Test result:
Failed to
Pop Data Validation MyStack = [] null Fail
execute
Test code:
Page. 31
Test result:
MyStack = [3,
top Normal 4 4 Pass
4]
Test code:
Test result:
Failed to
top Data Validation MyStack = [] null Fail
execute
Test code:
Test result:
MyStack = [3,
countItems Normal 2 2 Pass
4]
Test code:
Test result:
Page. 32
Test result:
MyStack = [3,
printAllNodes Normal 0 0 Pass
4]
Test code:
13
Test result:
14
Test result:
Queue ADT:
15
MyQueue
Page. 33
Test result:
16
Test result:
MyQueue =
enQueue Data Validation [3, 4, 5] False False Fail
enQueue(1)
Test code:
public class Test2 {
public static void main(String args[]) {
Queue<Integer> myQueue = new Queue<Integer>(3);
myQueue.enQueue(3);
myQueue.enQueue(4);
17 myQueue.enQueue(5);
if (!myQueue.enQueue(1)) System.out.println("Failed to enqueue.");
myQueue.printAllNodes();
}
}
Test result:
MyQueue =
deQueue Normal 3 3 Pass
[3, 4]
Test code:
18
Test result:
Page. 34
deQueue Normal MyQueue = [4] 4 4 Pass
Test code:
19
Test result:
20
Test result:
21
Test result:
22
Test result:
MyQueue =
23 hasItem Normal True True Pass
[3, 4, 5]
Page. 35
Test code:
public class Test2 {
public static void main(String args[]) {
Queue<Integer> myQueue = new Queue<Integer>(3);
myQueue.enQueue(3);
myQueue.enQueue(4);
myQueue.enQueue(5);
if (myQueue.hasItem()) {
System.out.println("The Queue has item.");
}
}
}
Test result:
MyQueue =
printAllNodes Normal <- 3 <- 4 <- 3 <- 4 Pass
[3, 4]
Test code:
public class Test2 {
public static void main(String args[]) {
Queue<Integer> myQueue = new Queue<Integer>(3);
myQueue.enQueue(3);
25 myQueue.enQueue(4);
myQueue.printAllNodes();
}
}
Test result:
Page. 36
}
}
Test result:
myQueue.enQueue(1);
myQueue.enQueue(2);
myQueue.enQueue(3);
System.out.println("Current Queue:");
myQueue.printAllNodes();
27 System.out.println("Reversed Queue is:");
Main.reverseQueue(myQueue);
myQueue.printAllNodes();
}
}
Test result:
Page. 37
System.out.println("Current Queue:");
myQueue.printAllNodes();
System.out.println("Reversed Queue is:");
Main.reverseQueue(myQueue);
myQueue.printAllNodes();
}
}
Test result:
True, True,
Stack1 = [1, 2, 3]
compareStacks Normal Preserve two Preserve two Pass
Stack2 = [1, 2, 3]
Stacks data Stacks data
Test code:
public class Test2 {
public static void main(String args[]) {
Stack<Integer> firstStack = new Stack<Integer>(20);
firstStack.push(1);
firstStack.push(2);
firstStack.push(3);
Compare two
31 System.out.println("First Stack:"); firstStack.printAllNodes();
stacks
Stack<Integer> secondStack = new Stack<Integer>(20);
secondStack.push(1);
secondStack.push(2);
secondStack.push(3);
System.out.println("Second Stack:"); secondStack.printAllNodes();
System.out.println(Main.compareStacks(firstStack, secondStack) ? "Two stacks equal" :
"Two stacks not equal");
System.out.println("First Stack:"); firstStack.printAllNodes();
System.out.println("Second Stack:"); secondStack.printAllNodes();
}
}
Page. 38
Test result:
True,
Data Stack1 = [] Failed to
compareStacks Preserve two Fail
Validation Stack2 = [] execute
Stacks data
Test code:
public class Test2 {
public static void main(String args[]) {
Stack<Integer> firstStack = new Stack<Integer>(20);
System.out.println("First Stack:"); firstStack.printAllNodes();
False, False,
Data Stack1 = [1, 2, 3]
compareStacks Preserve two Preserve two Pass
Validation Stack2 = [1, 2, 4]
Stacks data Stacks data
Test code:
public class Test2 {
public static void main(String args[]) {
Stack<Integer> firstStack = new Stack<Integer>(20);
firstStack.push(1);
firstStack.push(2);
33 firstStack.push(3);
System.out.println("First Stack:"); firstStack.printAllNodes();
Page. 39
System.out.println("Second Stack:"); secondStack.printAllNodes();
}
}
Test result:
False, False,
Data Stack1 = [1, 2]
compareStacks Preserve two Preserve two Pass
Validation Stack2 = [1, 2, 3]
Stacks data Stacks data
Test code:
public class Test2 {
public static void main(String args[]) {
Stack<Integer> firstStack = new Stack<Integer>(20);
firstStack.push(1);
firstStack.push(2);
System.out.println("First Stack:"); firstStack.printAllNodes();
False, False,
Data Stack1 = [1, 2, 4]
compareStacks Preserve two Preserve two Pass
Validation Stack2 = [1, 1]
Stacks data Stacks data
Test code:
public class Test2 {
35 public static void main(String args[]) {
Stack<Integer> firstStack = new Stack<Integer>(20);
firstStack.push(1);
firstStack.push(2);
firstStack.push(4);
System.out.println("First Stack:"); firstStack.printAllNodes();
Page. 40
Stack<Integer> secondStack = new Stack<Integer>(20);
secondStack.push(1);
secondStack.push(1);
System.out.println("Second Stack:"); secondStack.printAllNodes();
System.out.println(Main.compareStacks(firstStack, secondStack) ? "Two stacks equal" :
"Two stacks not equal");
System.out.println("First Stack:"); firstStack.printAllNodes();
System.out.println("Second Stack:"); secondStack.printAllNodes();
}
}
Test result:
False, False,
Data Stack1 = [1]
compareStacks Preserve two Preserve two Pass
Validation Stack2 = [1, 2, 3]
Stacks data Stacks data
Test code:
public class Test2 {
public static void main(String args[]) {
Stack<Integer> firstStack = new Stack<Integer>(20);
firstStack.push(1);
System.out.println("First Stack:"); firstStack.printAllNodes();
False, False,
Data Stack1 = []
compareStacks Preserve two Preserve two Pass
37 Validation Stack2 = [1, 2, 3]
Stacks data Stacks data
Test code:
Page. 41
public class Test2 {
public static void main(String args[]) {
Stack<Integer> firstStack = new Stack<Integer>(20);
System.out.println("First Stack:"); firstStack.printAllNodes();
False, False,
Data Stack1 = [1, 2]
compareStacks Preserve two Preserve two Pass
Validation Stack2 = []
Stacks data Stacks data
Test code:
public class Test2 {
public static void main(String args[]) {
Stack<Integer> firstStack = new Stack<Integer>(20);
System.out.println("First Stack:"); firstStack.printAllNodes();
firstStack.push(1);
firstStack.push(2);
Page. 42
3.2. Evaluation
There’re 38 test cases have been implemented as in above table. However, 2 test cases failed due to lack
of validation.
Firstly, MyQueue.printAllNodes (1) function of Queue ADT is not an important feature of the Data Type.
In process of implementing, the author has missing a checking step before executing the print out loop.
This cause a “silent error” that the program doesn’t print out anything but in the right way it should show
a message to notice that there’s no element in the Queue.
Secondly, same as (1), MyStack.printAllNodes, it’s not a compulsory function that means the author hasn’t
implemented checking expression for this function. That cause the similar error as in the above issue.
Thirdly, a same issue, MyStack.pop() also doesn’t have checking for number of currently available elements
before taking out the value. Technically, this is not a Logic error since whenever the top is null, we cannot
take out any more element. However, in the runtime process it can break the program. As a result, this is
a big problem since it directly affects on the program.
These three fail tests are not really important. However, it directly affects on the quality of the program
since it doesn’t let the author know about the issue that occurred in the run time. The checking step before
print out will save un-necessary steps and improve the performance of the program. Further, in other
problems, strict checking will ensure the best performance for the algorithm and reduce the Time
Complexity for the program.
As a sequence, test 26 also failed since it takes advantage of MyStack.pop(). MyStack.pop only works if
there’re remaining elements in the Stack, otherwise, it will cause a fatal error. Due to it, test 26 failed
because two imported Stacks are blank (or also one of them will have same result of fatal error).
The solution for the problem is thinking about special cases before implementing any algorithms. Through
this step, in implementing, the developer will be noticed about issues that may occur and able to
implement an appropriate solution for the problem.
Page. 43
4. How Asymptotic Analysis Measure Effectiveness of an Algorithm
Asymptotic analysis of an algorithm refers to defining the mathematical bound/framing of its run-time
performance. Using asymptotic analysis, we can very well conclude the best case, average case, and worst-
case scenario of an algorithm. Asymptotic analysis is input bound i.e., if there's no input to the algorithm,
it is concluded to work in a constant time. Other than the "input" all other factors are considered constant.
(Garibel, 2018)
Asymptotic analysis refers to computing the running time of any operation in mathematical units of
computation. For example, the running time of one operation is computed as f(n) and may be for another
operation it is computed as g(n2). This means the first operation running time will increase linearly with
the increase in n and the running time of the second operation will increase exponentially when n
increases. Similarly, the running time of both operations will be nearly the same if n is significantly small.
(Garibel, 2018)
Page. 44
4.1. Theta Notation (Θ-notation)
Theta notation encloses the function from above and below. Since it represents the upper and the lower
bound of the running time of an algorithm, it is used for analyzing the average case complexity of an
algorithm. (Swellson, 2019)
The above expression can be described as a function f(n) belongs to the set Θ(g(n)) if there exist positive
constants c1 and c2 such that it can be sandwiched between c1g(n) and c2g(n), for sufficiently large n.
(Swellson, 2019)
If a function f(n) lies anywhere in between c1g(n) and c2g(n) for all n ≥ n0, then f(n) is said to be
asymptotically tight bound. (Swellson, 2019)
Page. 45
4.2. Big-O Notation (O-notation)
Big-O notation represents the upper bound of the running time of an algorithm. Thus, it gives the worst-
case complexity of an algorithm. (Swellson, 2019)
The above expression can be described as a function f(n) belongs to the set O(g(n)) if there exists a positive
constant c such that it lies between 0 and cg(n), for sufficiently large n. (Swellson, 2019)
For any value of n, the running time of an algorithm does not cross time provided by O(g(n)). (Swellson,
2019)
Since it gives the worst case running time of an algorithm, it is widely used to analyze an algorithm as we
are always interested in the worst case scenario. (Swellson, 2019)
Page. 46
4.3. Omega Notation (Ω-notation)
Omega notation represents the lower bound of the running time of an algorithm. Thus, it provides best
case complexity of an algorithm.
The above expression can be described as a function f(n) belongs to the set Ω(g(n)) if there exists a positive
constant c such that it lies above cg(n), for sufficiently large n.
For any value of n, the minimum time required by the algorithm is given by Omega Ω(g(n)).
Page. 47
5. Two Ways Measuring Efficiency of An Algorithm
There are two ways to measure the effectiveness of an algorithm:
We use something called the "Big O notation" to express the time complexity of an algorithm. The Big O
notation is a language that we use to characterize an algorithm's time complexity. It's how the success of
multiple approaches to an issue is compared and helps us make decisions.
The runtime of an algorithm is expressed by the Big O notation in terms of how fast it grows relative to the
input (this input is called 'n'). Thus, if we suggest, for instance, that an algorithm's run time grows" on the
order of the input size, "we will state that as" O(n). If we suggest that an algorithm's run time grows "on
the order of the square of the input 's dimension," we will express it as "O(n2)."
• The speed of the computer: CPU (not just clock speed), I/O, etc.
• The compiler, compiler options.
• The quantity of data - ex. search a long list or short.
• The actual data - ex. in the sequential search if the name is first or last.
Page. 48
• Order of magnitude / asymptotic categorization-This utilizes coarse categories and offers a general
performance idea. If algorithms fall into the same category, if the size of the data is small, or if
output is critical, then you can look more closely at the next method.
• Estimation of running time:
By analysis of the code we can do:
o Operation counts - select operation(s) that are executed most frequently and determine
how many times each is done.
o Step counts - determine the total number of steps, possibly lines of code, executed by the
program.
• By execution of the code we can:
o Benchmark - run the program on various data sets and measure the performance.
o Profile - A report on the amounts of time spent in each routine of a program, used to find
and tune away from the hot spots in it.
o Worst case: the worst-case runtime complexity of the algorithm is defined by the maximum
number of steps taken on any instance of size n.
o Average case: the average case runtime complexity of the algorithm is defined by an average
number of steps taken on any instance of size n.
o Best case: the best-case runtime complexity of the algorithm is defined by the minimum number
of steps taken on any instance of size n.
One algorithm may run faster on some inputs than others of the same size. We may want to denote the
runtime of an algorithm as a function of the input size obtained by averaging on all possible inputs of the
same size. However, an average case analysis is often a challenge. Averaging case analysis also allows one
to measure estimated run times based on a given input distribution, and also involving complex probability
theory. The worst-case analysis is of special importance in real-time computation since it is important to
know how much worst-case time it will take to ensure that the algorithm will still be completed on time.
The word best-case output is used to define how an algorithm performs under optimum conditions.
Page. 49
5.2 Space efficiency
There are certain situations under which space/memory used must be evaluated. For example, for large
amounts of data or for the programming of embedded systems.
• Instruction space: Affected by the compiler, compiler options, target computer (CPU)
• Data space: Affected by the data size/dynamically allocated memory, static program variables,
• run-time stack space: Affected by the compiler, run-time function calls, and recursion, local
variables, parameters.
Memory limits provide details on the planned complexity of space. You can estimate the number of
variables you can declare in your programs. In short, if you have a constant number of variables, you also
have a constant complexity of space: in the Big-O notation, this is O (1). If you need to declare an array of
n-elements, you have linear space complexity — O(n). More precisely, the complexity of space is the
amount of memory used to conduct the computation. It contains all variables, both global and local,
dynamic pointer data structures and, in the case of recurrence, the contents of the stack. Input data can
also be used, depending on the convention. Space complexity is more difficult to quantify than time
complexity, since not all of these variables and data structures can be needed at the same time. Global
variables exist and occupy memory all the time; local variables (and additional information stored on the
stack) can only exist when the function is invoked.
Page. 50
6. Trade-off When Specifying an ADT
“Abstract data types introduce an abstraction barrier between those who implement a data type and those
who use it.” - (Clinger, 2010)
There’re a lot of well-known tradeoffs in computer development. Some example is: The time-space
tradeoff, where often you can use more memory to compute an answer faster. Or the time-accuracy
tradeoff, where you can compute an approximate answer quickly, and a precise one given more time.
Abstract Data Type brings a lot of benefits in development like: Representation independence, modularity,
interchangeability of Parts. However, for achieving those benefits, the program also has some trade-off
for that.
Data Encapsulation is one of the most important property of ADT, it’s used to hide the internal
representation, or state, of an object from the outside and can be called information hiding. However, by
applying Encapsulation, the program not just prevent unwanted accesses from outside but also restrict
any effort to accessing or modifying internal information. This can lead to an uncomfortable feeling in
development or need to open more methods for interacting with data inside the object.
Page. 51
7. Benefits of Using Implementation Independent Data Structures
Almost all required Data Structures are pre-defined in the programming language. However, in some
situation, the programmer has to define own Data Structure for special purposes.
Back to advantages and disadvantages of Abstract Data Types (Tradeoffs), for good purposes when using
own ADT, the programmer has to paid for the performance using memory space. Also, in Independent
Data Structures, the tradeoffs can be various kinds: Slower, lack of features, lack of testing, not well
defined, not well structured, …
However, to the bright side, Independent Data Structures bring big advantages.
When using Independent Data Structures, the program becomes independent from the representation of
the data, so the representation will be improved and avoid breaking the entire program. This often be
called as “Representation Independent”.
Furthermore, in the development, when the programmers work in team, parts of the program will be
developed independently and build dependencies modules for the whole project. Implementing
Independent Data Structure will represent the data independence and parts of the program will be less
depend on other parts and how they’re implemented. This is called “Modularity”.
And the final advantage in the author’s opinion is “customization”. Not using pre-defined Data Structures
enables Development Team a higher level of customization and reduce unnecessary parts of the system.
Higher customization also mean a higher controllable in the project when the management will be deep
down to the lowest level of the Data Structure.
Page. 52
Conclusion
Through this document, the author has discussed about Stack ADT, Queue ADT and their implementation
in the project. Problems are also discussed and two questions have been answered clearly:
• Write a function to reverse the order of a queue using a stack. Include proper annotations in your
solution. What is the worst-case run time complexity of this function if there are n elements in the
queue? Explain.
• Write a function that takes two stacks and returns true if the stacks contain the same elements in
the same order. After the operations, both stacks must remain in the original order. You are
allowed to use additional stacks or queues as needed, but no other data structures. What is the
worst-case run time complexity of your code if each stack contains n elements? Explain.
Through the report, the author has discussed in detail the implementation of two data structures and how
to measure the effectiveness of the algorithms involved. The report also assessed the use of ADT in design
and development, including complexity, tradeoffs and benefits.
Page. 53
References
S.Adamchik, V., 2009. Stacks and Queues. [Online]
Available at: https://www.cs.cmu.edu/~adamchik/15-
121/lectures/Stacks%20and%20Queues/Stacks%20and%20Queues.html
Page. 54