Data Structure
Collections
Definition
- A collection is a group of items that are treated as a conceptual unit
- Collections can be homogeneous, meaning all items must be of the same type, or heterogeneous, meaning items can be of different types
Types of Collections
Linear Collections
- Items are ordered by position
- Examples include grocery lists, stacks of dinner plates, or a queue of customers
- Stacks and queues are examples of linear collections
Hierarchical Collections
- These structures resemble an upside-down tree
- Examples include file directory systems or a company’s organisational tree
- D3’s parent is D1, and its children are D4, D5, and D6 in such a structure
- Binary trees are a type of hierarchical collection
Graph Collections
- Each data item can have many predecessors and many successors
- D3’s neighbours would be its predecessors and successors
- Examples include maps of airline routes between cities
Unordered Collections
- Items are not in any particular order
- This means that an item’s predecessor or successor does not exist
- An example is a bag of marbles.
Operations on Collections
- Traversal: Visiting each item in a collection.
- Search and retrieval: Finding a given target item or an item at a specific position.
- Insertion: Adding an item to a collection at a given position.
- Removal: Deleting a given item or the item at a given position.
- Size Determination: Finding the number of items a collection contains.
Array
Definition
- An array represents a sequence of items of the same data type
- Items can be accessed, retrieved, stored, or **replaced **at given index positions
Data Structure
Random Access

- Arrays support random access, meaning any item can be accessed directly using its index, because **data **is stored in contiguous memory
- The **address **of an item is calculated as base address + (index * k), where k is the memory cells per item
Static Memory
- Arrays are static, meaning their capacity or length is determined at compile time, requiring you to specify the size with a constant
Physical and Logical Size

- You must track both the physical size (total number of cells) and the logical size (number of items currently in it)
- This is to avoid reading “garbage” data
- If the logical size is 0, the array is empty
- The **index of the last item **is logical size - 1
- If logical size equals physical size, there is no more room for data
Operations on Arrays
Insertion
- **Check **for available space before attempting an insertion
- Shift items from logical end of array to target index position down by one
- To open “hole” for new item at target index
- Assign new item to target index position
- Increment logical size by one
Removal

- Shift items from **next target position **to the logical end of the array up by one
- To close “hole” left by removed item at target index
- Decrement logical size by one
Linked List
Definition
- Linked lists are concrete linear data types used to implement various collections
- A key characteristic of linked structures is that they decouple the logical sequence of items from any ordering in memory
- This means that they use a dynamic memory representation scheme
Data Structure
Node

- This is done through the use of a node, of which stores data and a pointer
- There is also the head, where it is a pointer referencing the first node or a null pointer
Operations on Linked Structures
Traversal

- In order to visit each node without deleting it, we can use a temporary pointer variable which traverses the linked list
- Stops the process when the current node points to a null pointer
Searching
- Resembles a traversal, but two possible stopping conditions:
- Data is not present
- Node data that equals the target item
Replacement
- Replacement operations employ traversal pattern
- If the target item is not present, no replacement occurs
- If the target is present, the new item replaces it
Insertion

- To add a given item or the item at a given position, traverse the list to find the position
- There are two cases to consider:
Insert at front: Updates the head pointer
Insert at position i: Updates the pointer of the (i - 1)th nodeDeletion

- To remove a given item or the item at a given position, traverse the list to search for the node to be deleted
- As in insertion, must consider two cases:
Delete at front: Updates the head pointer
Delete at position i: Updates the pointer of the (i - 1)th nodeArray Representation
Free Space List
Stack
- LIFO (last-in-first-out) structure which is completely restricted to the top
- It has two basic operations: **push **and pop
Common Problems
Bracket Expressions
- Compilers need to determine if the brackets in expressions are balanced
Determining the balance of bracket expression
- Iterate through the expression
- If the character is an opening bracket, push a closing bracket of the same type onto the stack
If the character is a closing bracket, peek the top of the stack:- If stack is empty, expression is invalid
- If top of stack is not the same closing bracket, expression is invalid
- If the end of the expression is reached, stack should be empty, otherwise expression is invalid
Code
#include <iostream>
#include <stack>
#include <string>
bool bracketex(std::string expr){
std::stack<char> st;
for (int i = 0; i < expr.length(); i++){
if (expr[i] == '(')
st.push(')');
else if (expr[i] == '[')
st.push(']');
else if (expr[i] == '{')
st.push('}');
else if (st.empty() || st.top() != expr[i])
return 0;
else
st.pop();
}
return 1;
}Arithmetic Expression
- An arithmetic expression can be represented by two forms:
- Infix form: Each operator is located **between **its operands. e.g. A + B
- Postfix form: An operator immediately follows its operands. e.g. A B +
- Operands appear in the same order, but operators do not in both forms
- Infix forms might require parentheses, but the postfix form never does
- Infix evaluation involves rules of precedence, but the postfix evaluation applies to operators as soon as they are encountered
Converting Infix to Postfix

- Start with an empty postfix expression and an empty stack, the stack will hold operators and left parentheses
- Iterate through infix expression from left to right
- On encountering an operand, append it to postfix expression
- On encountering a ‘(‘, push it onto the stack
- On encountering an operator
- Pop off the stack all operators with equal or higher precedence
- Append them to postfix expression
- Push scanned operator onto stack
- On encountering a ‘)’
- Pop operators from stack and add to postfix expression
- Stop when meeting matching ‘(‘, which is discarded
- On encountering the end of the infix expression, pop remaining operators from the stack to the postfix expression
Code
#include <iostream>
#include <stack>
#include <string>
#include <cctype>
int prec(char op) {
if (op == '*' || op == '/') return 2;
if (op == '+' || op == '-') return 1;
return 0;
}
std::string inf_to_post(std::string expr){
std::stack<char> st;
std::string new_expr;
for (int i = 0; i < expr.length(); i++){
if (expr[i] == ' ') continue;
else if (isdigit(expr[i]))
new_expr = new_expr + expr[i] + ' ';
else if (expr[i] == '(') st.push('(');
else if (expr[i] == ')'){
while (!st.empty() && st.top() != '('){
auto tmp = st.top();
st.pop();
new_expr += tmp + ' ';
}
st.pop();
}
else {
while (!st.empty() && prec(st.top()) >= prec(expr[i])){
auto tmp = st.top();
st.pop();
new_expr += tmp + ' ';
}
st.push(expr[i]);
}
}
while (!st.empty()){
auto tmp = st.top
st.pop();
new_expr += tmp + ' ';
}
return new_expr;
}Evaluating Postfix Expression
- Scan across the postfix expression from left to right
- On encountering an operand, push it onto the stack
- On encountering an operator
- Pop the top two operands
- Apply the operator to the operands
- Push the result onto the stack
- Continue scanning until you reach expression’s end
Code
Only works for single digits
float eval_postfix(std::string expr){
std::stack<float> st;
for (int i = 0; i < expr.length(); i++){
if (expr[i] == ' ') continue;
else if (isdigit(expr[i])) st.push(expr[i]-'0');
else {
float a = st.top(); st.pop();
float b = st.top(); st.pop();
float res;
char op = expr[i];
switch (op){
case '+': res = b + a; break;
case '-': res = b - a; break;
case '*': res = b * a; break;
case '/': res = b / a; break;
}
st.push(res);
}
}
float final_res = st.top();
st.pop();
return final_res;
}Memory Management
Function Call
The run-time system must keep track of various details:
- Associating variables with data objects stored in memory so they can be referenced
- Remembering the return address of the instruction where function is called to carry on to the next instruction after finishing execution
- Allocating memory for a function’s arguments and temporary variables, which exist only during the execution of that function
Stack Frame

- Whenever a subroutine is called, a stack frame is created to store the current environment for that function.
- This includes: parameters, temporary variables, return address and return value
What data structure should be used to store stack frames?
- Problem:
Function A can call Function B
Function B can call Function C- When a function calls another function, it interrupts its own execution and needs to be able to resume its execution in the same state it was in when it was interrupted
- When Function C finishes, control should return to Function B
- When Function B finishes, control should return to Function A
- Order of returns from a function is the reverse of function invocations (LIFO)
- Use a stack to store the activation records, which is called the run-time stack.
What happens when a function is called?
- Push a copy of its activation record onto the run-time stack
- Copy its arguments into the parameter spaces
- Transfer control to the starting address of the body of the function
- Top stack frame in the run-time stack is always the function currently being executed
What happens when a function terminates?
- Pop the stack frame of terminated function from the run-time stack
- Use new top stack frame to restore the environment of the interrupted function and resume execution of the interrupted function
Queue
- FIFO (first-in-first-out) structure
- **Insertions **are restricted to the rear
- **Removals **are restricted to the front
- It has two basic operations: **enqueue **and dequeue
Common Array Implementations
Method 1
Idea
- Fixes variable **front **at position 0
- Variable **rear **points to last item at position n-1
Evaluation
- Efficient enqueue operation: All items just placed at position n
- Inefficient dequeue operation: All items must be shifted to the left
Method 2
Idea
- Maintain a second variable **front **that points to item at front of queue
- Starts at 0 and **advances **as items are dequeued
- Cells to the **left **of the queue’s front pointer are **unused **until we shift all elements left, which we do whenever the rear pointer is about to run off the end
Method 3
Idea
- Use a circular array implementation
- Variable **rear **starts at –1; Variable **front **starts at 0
- **Front **chases rear pointer through the array
- When a **pointer **is about to run off the end of the array, it is reset to 0
- This wraps the queue around to the **beginning without **the moving any items


