Midterm
Midterm
I. Theory
1. Array
An ARRAY is a collection of variables all of the same TYPE.
a) Linear search
DEFINITION: Linear search is a simple algorithmi.c approach for finding a
specific element within an unsorted or sorted collection of elements. The algorithm
starts by comparing the target value to the first element of the collection and
1
continues searching sequentially through the collection until either the target value
is found or the end of the collection is reached.
b) Binary search
DEFINITION: Binary search is an algorithmic approach for finding a specific
2
int right = arr.length - 1;
3
}
4
left value: 0
right value 16
mid value: 8
the value in mid index: 13
left value: 0
right value 7
mid value: 3
the value in mid index: 7
left value: 4
right value 7
mid value: 5
the value in mid index: 10
left value: 6
right value 7
mid value: 6
5
the value in mid index: 11
Target found at index 6
3. Big O notation
- To measure the EFFICIENCY of algorithms.
- Step: O(N^2), O(N), O(logN), O(1)
a) Constant – O(1)
The time needed by the algorithm is not depend in size of items.
b) Proportional to N – O(N)
- Linear search has a time complexity of O(n), where n is the size of the
collection. This means that in the worst case, the algorithm may need to
examine every element of the collection before finding the target value.
6
c) Proportional to log(N) – O(log N)
- Binary search is a very efficient algorithm for searching sorted collections,
with a time complexity of O(log n), where n is the size of the collection.
For (1->m)
For(1->n) -> m*n
7
For (1->m)
For(1->n)
If (condition) -> 1
-> m + n + 1 -> m
4. Simple Sorting
- The algorithm with the smallest number of comparisons is Insertion Sort.
- The algorithm with the smallest number of swaps is Selection Sort.
- The algorithm with the smallest number of copies is Insertion Sort.
- The speed of the three types of sort from fastest to slowest is: Insertion Sort,
Selection Sort, Bubble Sort
a) Bubble Sort
- Compare the first item in the first two positions (e.g. the left most)
- If the first one is larger, swap with the second
8
- Move one position right.
- Stop comparing at (n-2).
- Thus number of comparisons is computed as:
(n-1)+(n-2)+… + 1 = n(n-1)/2.
- A swap will occur half the time: For n2/2 comparisons, we have n2/4 swaps.
- Both swaps and compares are proportional to n2 (Ignore the 2 and 4)
- Complexity of Bubble Sort is O(n2)
- Space complexity: O(1)
CODE EXAMPLE:
(1) BubbleSort algorithm.
public static void BubbleSort(int[] arr){
int nElems = arr.length;
for(int out = nElems - 1; out>1; out--){
for(int in = 0; in < out; in++) {
if(arr[in] > arr[in+1]){
swap(arr, in, in+1);
}
9
}
}
}
(3) To sort array descending (Đổi dấu > thành < ở if)
(4) To use 2 forward loops
public static void bubbleSortTwoLoops(int[] arr){
int nElems = arr.length;
10
boolean swapped;
do {
swapped = false;
for(int in = 0; in < nElems - 1; in++) {
if(arr[in] > arr[in+1]){
swap(arr, in, in+1);
swapped = true;
}
}
} while (swapped);
}
(5) Testing
public static void main(String[] args) {
int[] arr = {4,1,6,9,2,5,8,0,1,4};
displayArray(arr);
BubbleSort(arr);
displayArray(arr);
bubbleSortRightToLeft(arr);
11
displayArray(arr);
bubbleSortDescending(arr);
displayArray(arr);
bubbleSortTwoLoops(arr);
displayArray(arr);
}
RESULT:
4169258014
0112445689
0112445689
8965442110
0112445689
12
- Swap with the first element.
- Next pass, move one position right.
- Repeat until all are sorted.
- Time complexity: O(n2)
- Space complexity: O(1)
So, in one pass, you have made n comparisons but possibly ONLY ONE Swap!
- With each succeeding pass:
o One more item is sorted and in place
o One fewer item needs to be considered.
CODE EXAMPLE:
(1) SelectionSort algorithm
public static void SelectionSort(int[] arr){
int nElems = arr.length;
for(int out = 0; out<nElems - 1; out++){
int min = out;
for(int in = out+1; in<nElems; in++){
13
if(arr[in] < arr[min]){
min = in;
}
}
swap(arr, out, min);
}
}
14
}
}
bubble sort
CODE EXAMPLE:
public static void InsertionSort(int[] arr) {
int nElems = arr.length;
for (int i = 1; i < nElems; i++) {
int key = arr[i];
int j = i - 1;
15
while (j >= 0 && arr[j] > key) {
arr[j + 1] = arr[j];
j--;
}
arr[j + 1] = key;
}
}
5. Stack
- Stack is a data structure that follows the Last-In-First-Out (LIFO) principle,
meaning the last element added to the stack is the first one to be removed. In
a stack, elements are added and removed from the top (accessible item) of
the stack.
- Operations: Push, Pop, Peek. (All complexity of them are O(1))
- Properties: Stack Overflow (Full) – Stack size (is full?), Stack Underflow
(Empty) – Number of element (is empty?)
CODE EXAMPLE:
16
Stack<Integer> myStack = new Stack<>();
// pushing elements to the stack
myStack.push(5);
myStack.push(10);
myStack.push(15);
System.out.println(myStack);
17
Popped element: 15
[5, 10]
Peeked element: 10
[5, 10]
6. Queue
- Queue is a data structure that follows the FIFO (First In First Out) principle.
It is an abstract data type that can be implemented using an array or a linked
list.
- A queue can be used to store a collection of elements and allow them to be
accessed in the order they were added. Elements are added to the back of the
queue and removed from the front of the queue.
- Operations: Insert/ Enqueue, Remove/ Dequeue. (Time complexity - O(1))
- Properties: Full – Queue size (is full?), Empty – Number of element (is
empty?)
- Accessible item: Head – 1st element added, Tail – last element added.
18
CODE EXAMPLE:
// Create a queue of integers
Queue<Integer> queue = new LinkedList<>();
// Enqueue elements to the queue
queue.add(10);
queue.add(20);
queue.add(30);
queue.add(40);
queue.add(50);
// Dequeue and print the first element in the queue
int removedElement = queue.remove();
System.out.println("Removed Element: " + removedElement);
// Print the remaining elements in the queue
System.out.println("Elements in Queue: " + queue);
// Enqueue another element to the queue
queue.add(60);
// Dequeue and print the first element in the queue
removedElement = queue.remove();
19
System.out.println("Removed Element: " + removedElement);
// Print the remaining elements in the queue
System.out.println("Elements in Queue: " + queue);
RESULT:
Removed Element: 10
Elements in Queue: [20, 30, 40, 50]
Removed Element: 20
Elements in Queue: [30, 40, 50, 60]
7. Priority Queues
- Efficiency of priority queue: Insertion (if use ARRAY: O(N)), Deletion
O(1).
- One difference between a priority queue and an ordered array is that the
highest priority item can be extracted easily from the priority queue but not
from the array.
CODE EXAMPLE:
// Create a priority queue
20
PriorityQueue<Integer> pq = new PriorityQueue<Integer>();
// Add elements to the priority queue
pq.add(5);
pq.add(2);
pq.add(8);
pq.add(1);
pq.add(6);
// Print the top element of the priority queue
System.out.println("Top element: " + pq.peek());
// Remove the top element of the priority queue
pq.poll();
// Print all elements of the priority queue
while (!pq.isEmpty()) {
System.out.print(pq.poll() + " ");
}
RESULT:
Top element: 1
21
2568
22
A A A Write operand to postfix
+ A+ A + If stack empty, push onThis
B A+B AB + Write operand to output
* A+B* AB + Stack not empty, so pop opTop
A+B* AB + opThis is *, opTop is +,
opTop<opThis, so push opTop
A+B* AB +* Then push onThis
C A+B*C ABC +* Write operand to output
End A+B*C ABC* + Pop leftover item, output it
A+B*C ABC*+ Pop leftover item, output it
Translation Rules Applied to A+B*C
23
9. Simple linked list
10. Double-ended list
11. Sorted list
12. Doubly linked list
13. List with iterators
14. Triangular Numbers
15. Factorials
16. A Recursive Binary Search
II. Code
1. Simple code
a) Code function to display every element in an Array.
public static void displayArray(int[] arr) {
for (int i = 0; i < arr.length; i++) {
System.out.print(arr[i] + " ");
}
System.out.println();
24
}
2. Lab
III. Midterm exam 2022
1. Sort (20 marks)
Given array B
Inde 0 1 2 3 4 5 6 7 8 9 10 11 12
x
Data 72 4 12 53 15 2 8 16 40 21 7 87 35
Use the merge sort algorithm to sort array B in ascending order by filling the table
below:
25
Action 0 1 2 3 4 5 6 7 8 9 10 11 12
Consider values in 72 4 12 53 15 2 8
the range from 0 to 6
…
Sort value in the rage
…
26
- dequeue() - The first element 2 is removed and returned. The queue becomes [0]
- enqueue(2) - The queue becomes [0, 2] - enqueue(2) - The queue becomes [0, 2, 2]
- dequeue() - The first element 0 is removed and returned. The queue becomes [2, 2]
- dequeue() - The next element 2 is removed and returned. The queue becomes [2]
- enqueue(9) - The queue becomes [2, 9]- enqueue(1) - The queue becomes [2, 9, 1]
- dequeue() - The first element 2 is removed and returned. The queue becomes [9, 1]
- enqueue(7) - The queue becomes [9, 1, 7] - enqueue(6) - The queue becomes [9, 1, 7, 6]
- dequeue() - The first element 9 is removed and returned. The queue becomes [1, 7,
6]
- dequeue() - The next element 1 is removed and returned. The queue becomes [7, 6]
- enqueue(4) - The queue becomes [7, 6, 4]
- dequeue() - The first element 7 is removed and returned. The queue becomes [6, 4]
- dequeue() - The next element 6 is removed and returned. The queue becomes [4]
Therefore, the values returned after each dequeue() operation are: 2, 0, 2, 2, 9, 1, 9,
7, 6. The final state of the queue is [4].
27
b) What values are returned after each pop() during the
following series of stack operations, if executed upon an
initially empty stack?
push(5), push(3), pop(), push(2), push(0), pop(),
pop(), push(2), push(2), pop(), push(7), push(6), pop(), pop(),
push(4), pop(), pop().
The sequence of stack operations is:
- push(5) - The stack becomes [5] - push(3) - The stack becomes [5, 3]
- pop() - The last element 3 is removed and returned. The stack becomes [5]
- push(2) - The stack becomes [5, 2] - push(0) - The stack becomes [5, 2, 0]
- pop() - The last element 0 is removed and returned. The stack becomes [5, 2]
- pop() - The next element 2 is removed and returned. The stack becomes [5]
- push(2) - The stack becomes [5, 2] - push(2) - The stack becomes [5, 2, 2]
- pop() - The last element 2 is removed and returned. The stack becomes [5, 2]
- push(7) - The stack becomes [5, 2, 7] - push(6) - The stack becomes [5, 2, 7, 6]
- pop() - The last element 6 is removed and returned. The stack becomes [5, 2, 7]
- pop() - The next element 7 is removed and returned. The stack becomes [5, 2]
- push(4) - The stack becomes [5, 2, 4]
28
- pop() - The last element 4 is removed and returned. The stack becomes [5, 2]
- pop() - The next element 2 is removed and returned. The stack becomes [5]
Therefore, the values returned after each pop() operation are: 3, 0, 2, 2, 6, 7, 4, 2.
The final state of the stack is [5].
29
- Initialize two pointers i and j to point to the first node of the doubly-linked list.
- Traverse the doubly-linked list using the outer loop with pointer i, starting from the first
node.
- Within the outer loop, initialize a variable min to the current node i.
- Traverse the doubly-linked list using the inner loop with pointer j, starting from the next
node of i.
- Within the inner loop, compare the key value of each node with the key value of min.
- If a smaller key value is found, set min to the current node j.
- After the inner loop, swap the key value of i with the key value of min.
- Move the pointer i to the next node of the doubly-linked list.
- Repeat steps 4 to 9 until the end of the doubly-linked list is reached.
Here is the implementation of selectionSort() method:
30
while (i != last) {
Node min = i;
Node j = i.next;
while (j != null) {
if (j.key < min.key) {
min = j;
}
j = j.next;
}
if (min != i) {
int temp = i.key;
i.key = min.key;
min.key = temp;
}
i = i.next;
}
}
31
In this implementation, we have used two pointers, i and j, to traverse the doubly-linked
list. The min variable is used to keep track of the node with the smallest key value within the
inner loop. We have also used the key value to perform the comparison and swap operations.
The time complexity of the selectionSort() method is O(n^2) in the worst case, since we
are using two nested loops to traverse the doubly-linked list. The space complexity is O(1), since
we are only using a constant amount of extra space to perform the sorting.
32
IsFull
IsEmpty Store and check the Check whether the first Check whether the first
number of items in element is null. O(1) element is null. O(1)
the array. O(1)
33
public class StackUsingQueues<T> {
private Queue<T> queue1 = new LinkedList<>();
private Queue<T> queue2 = new LinkedList<>();
public T pop() {
if (queue1.isEmpty()) {
throw new IllegalStateException("Stack is empty");
}
while (queue1.size() > 1) {
queue2.offer(queue1.poll());
}
T element = queue1.poll();
Queue<T> temp = queue1;
queue1 = queue2;
34
queue2 = temp;
return element;
}
}
Now, let's analyze the time complexity of the push() and pop() operations in this
implementation:
- push(): Enqueuing an element into a queue takes constant time, so the time complexity of
the push() operation is O(1).
- pop(): Dequeuing an element from a non-empty queue takes constant time, so the time
complexity of moving all the elements except the last one from queue1 to queue2 is
O(n), where n is the number of elements in queue1. The time complexity of dequeuing
the last element from queue1 and swapping the names of queue1 and queue2 is also
O(n), but it is dominated by the first O(n) operation. Therefore, the time complexity of
the pop() operation is O(n).
In the worst case, when the stack is full and we need to move all the elements from queue1 to
queue2 to perform a pop operation, the time complexity of the pop() operation is O(n).
However, on average, the time complexity of the pop() operation is much lower, since we only
need to move a small number of elements from queue1 to queue2. Overall, this implementation
35
provides a reasonable trade-off between time and space complexity, since it uses two queues but
only one queue is non-empty at any given time.
36
- When popping an element from a stack, return the element at the index of the pointer and
decrement the corresponding pointer.
- To check if a stack is empty, check if the corresponding pointer is pointing to the first
element of the stack.
- To check if a stack is full, check if both pointers are pointing to the same index in the
array.
Here is the Java code for implementing two stacks using one array:
37
}
array = new int[size];
pointer1 = -1;
pointer2 = size;
}
38
int element = array[pointer1];
pointer1--;
return element;
}
39
array[pointer2] = element;
}
40
}
}
In this implementation, we have used two pointers, pointer1 and pointer2, to keep track
of the top of each stack. The push1() and pop1() methods are used for the first stack, while the
push2() and pop2() methods are used for the second stack. The isEmpty1(), isFull1(),
isEmpty2(), and isFull2() methods are used to check if a stack is empty or full.
The time complexity of the push1(), pop1(), push2(), and pop2() methods is O(1), since
they only involve incrementing or decrementing the corresponding pointer and accessing or
setting an element in the array. The space complexity of this implementation is O(N), since we
are using an array of size N to store the elements of both stacks.
41
Search for the value 97. Fill the following table with corresponding steps in the binary search
Step Lower bound Upper bound Considering Index Considering data Conclusion
1 0 9 5 29 Update UB
2 0 4 2 76 Update UB
3 0 1 1 98 Update LB
4 2 1 Stop program
Using shell sort algorithm to sort array B in ascending order by filling the table below
Action/ 0 1 2 3 4 5 6 7 8 9 10 11 12
Index
Data 80 4 3 50 30 1 50 88 2 0 15 43 11
42
3. Linked list (20 pts) – Same 2022
4. Queue (20 pts)
What is the best complexity of the enqueue, dequeue, isEmpty, and isFull
functions if a priority queue is implemented with different data structures?
Fill in the following table with suitable big O values and explanation if needed.
Data structure Array Double Ended Doubly Linked
Linked list List
Enqueue
Dequeue
IsEmpty
IsFull
a) Array
If a priority queue is implemented with an array, the best complexity of the enqueue
function is O(n), where n is the number of elements in the array. This is because we need to find
43
the correct position to insert the new element based on its priority, and this may require shifting
other elements in the array.
The best complexity of the dequeue function is O(1), as we can simply remove the first
element in the array, which is the one with the highest priority.
The best complexity of the isEmpty function is O(1), as we can simply check if the
number of elements in the array is zero.
The best complexity of the isFull function is O(1), as we can simply check if the number
of elements in the array is equal to the maximum capacity of the array.
44
The best complexity of the isFull function is not applicable, as there is no
fixed maximum capacity for a linked list.
45
5. Stack (10pts) – Same 2022
V. Midterm exam 2018
1. Binary search (20 pts)
Given array A
Inde 0 1 2 3 4 5 6 7 8 9
x
Data 120 90 75 70 29 25 21 11 2 0
Fill the following table with corresponding steps in the binary search
algorithm until 90 is found in A
Step Lower bound Upper bound Considering Considering Conclusion
(LB) (UB) Index data
1 0 9 5 25 Update UB
2
46
2. Sort (20 pts)
Given array B
Inde 0 1 2 3 4 5 6 7 8 9
x
Data 100 52 3 22 31 67 42 75 23 0
Sort array B in ascending order using the quicksort algorithm –
where the rightmost item is used as a pivot, by filling the following table
Action/Index 0 1 2 3 4 5 6 7 8 9
…………….
…………….
47
void push(int data); void insertFirst(int data);
int pop(); void insertLast(int data);
int peek(); int getFirst();
boolean isEmpty(); int getLast();
boolean isFull(); int deleteFirst();
} int deleteLast();
boolean isEmpty();
}
48
@Override
public void push(int data) {
list.insertFirst(data);
}
@Override
public int pop() {
if (isEmpty()) {
throw new RuntimeException("Stack is empty");
}
return list.deleteFirst();
}
@Override
public int peek() {
if (isEmpty()) {
throw new RuntimeException("Stack is empty");
}
49
return list.getFirst();
}
@Override
public boolean isEmpty() {
return list.isEmpty();
}
@Override
public boolean isFull() {
return false; // since we are using a linked list, the
stack can never be full
}
}
In this implementation, we use a double-ended linked list to represent the
stack. The push operation simply inserts the new element at the beginning of the
list using the insertFirst method, which takes O(1) time. The pop operation deletes
50
the first element of the list using the deleteFirst method, which also takes O(1)
time. The peek operation simply returns the value of the first element using the
getFirst method, which also takes O(1) time. The isEmpty operation checks if the
list is empty using the isEmpty method, which also takes O(1) time. Since we are
using a linked list, the isFull operation always returns false.
51
Push
Pop
Peek
IsEmpty
IsFull
52
private Stack<T> enqueueStack; // stack for enqueue
operations
private Stack<T> dequeueStack; // stack for dequeue
operations
public QueueUsingTwoStacks() {
enqueueStack = new Stack<>();
dequeueStack = new Stack<>();
}
public T dequeue() {
if (dequeueStack.isEmpty()) { // transfer elements from
enqueue stack to dequeue stack if the dequeue stack is empty
53
while (!enqueueStack.isEmpty()) {
dequeueStack.push(enqueueStack.pop());
}
}
return dequeueStack.pop(); // pop the top element from
the dequeue stack
}
54
}
55
- IsEmpty operation: Checking if both stacks are empty takes O(1)
time.
- Size operation: Adding the sizes of both stacks takes O(1) time.
Overall, the time complexity of each operation is very efficient, with
enqueue and isEmpty operations taking O(1) time and dequeue and size
operations taking O(1) amortized time.
56
57