Clastify logo
Subjects
Features
Review
HOT
Tutoring

B2.2: Data structures

Master IB Computer Science B2.2: Data structures with notes created by examiners and strictly aligned with the syllabus.

This content can't be printed from the browser. Use Export PDF with Pro for an offline copy.

IB Syllabus Requirements for Data structures

B2.2.1

Comparing static and dynamic structures

B2.2.2

Programming with arrays and Lists

B2.2.3

Stacks and last-in, first-out processing

B2.2.4

Queues and first-in, first-out processing

B2.2.1

COMPARING STATIC AND DYNAMIC STRUCTURES

Compare structures by looking at how they allocate memory and whether their capacity can change. These choices affect speed, memory usage and flexibility. Neither approach is always better; choose according to the problem.

Size and capacity

Once created, a static data structure has a fixed capacity. The elements can change, but the number of allocated positions cannot.

A dynamic data structure can change its size while a program runs. It manages storage so that elements can be added or removed without the programmer setting a permanent size at creation.

Size counts the elements currently stored; capacity counts how many elements the allocated storage can hold. Some capacity may therefore be unused.

Here, static refers to the structure's capacity, not Java's static keyword. A Java array is fixed-size even when its length is calculated while the program is running.

Memory allocation and resizing

A fixed-size array allocates storage for a set number of indexed positions. These form an indexed block, so an element's position can be calculated directly from its index. If the program needs a larger array, it must allocate a different one and copy the existing elements. The original array has not grown.

Java's ArrayList and Python's list are dynamic structures that use resizable backing arrays. Their storage usually has spare capacity. An added element takes a spare position if one is available. Otherwise, the structure allocates a larger backing array and copies the existing entries. Python lists store references to objects, rather than all the objects themselves, in the list's storage.

The figure compares a fixed-capacity array with a resizable backing array. Both start with A and B in capacity 3. After C is added, adding D requires the dynamic structure to obtain larger storage and copy A, B and C.

Removing an element reduces a dynamic list's size, but its capacity does not necessarily fall immediately. The list may keep reserved storage for later additions.

Comparing the trade-offs

FactorFixed-size arrayDynamic array-backed list
AllocationCapacity is chosen when the array is created.Capacity can increase automatically as elements are added.
Indexed accessDirect access to a position is fast.Direct indexed access is also fast.
Adding at the endFast if the program has reserved an unused position; going beyond capacity requires replacement storage.Usually fast, but an addition that triggers resizing also requires allocation and copying.
Inserting or removing near the beginningKeeping elements in order requires later elements to shift; the array length stays unchanged.Keeping elements in order also requires later elements to shift; the list's size changes.
Memory usageAllocation is predictable, but overestimating capacity wastes positions.Spare capacity and bookkeeping use memory; growth can temporarily require both old and new storage.
FlexibilitySuits a known, stable number of positions.Suits a collection whose number of elements changes.

Dynamic doesn't mean slower for every operation. Both arrays and array-backed lists support fast indexed access. Operations such as resizing and shifting elements account for the extra costs.

Choosing for a situation

A device that always records exactly 12 monthly totals can use a fixed-size array for simple, predictable allocation. The number of positions is already known, so dynamic resizing offers little benefit.

In a club registration system, the number of members is unknown. A dynamic list avoids the need to choose an arbitrary maximum. Occasional resizing and spare capacity are reasonable costs for allowing registrations and withdrawals.

Key terms: static data structures, dynamic data structures, memory allocation, resizing, speed, memory usage, flexibility.

B2.2.2

PROGRAMMING WITH ARRAYS AND LISTS

Construct programs by choosing a suitable structure, accessing positions with valid indices, and using loops to process its elements. Java uses fixed-size arrays and dynamic ArrayList objects; Python uses dynamic lists for both one-dimensional and two-dimensional collections.

One-dimensional and two-dimensional structures

An array is an indexed collection with a fixed number of positions. A one-dimensional array uses one index; a two-dimensional array uses a row index followed by a column index.

A list is an ordered collection whose elements can be accessed by position. Java's ArrayList and Python's list allow its size to change.

Indexing starts at 0 in both languages. A collection containing 3 elements has valid indices 0, 1 and 2; index 3 is outside its bounds.

For the following examples, a workshop records daily repair counts of 4, 7 and 5. A separate two-dimensional structure stores two rows: 2, 3, 4 and 5, 6, 7. The figure shows the one-dimensional indices and the two-dimensional row and column indices; row 1, column 2 contains 7.

Worked example: indexing and traversal

The Java and Python versions perform the same steps: change the middle daily count to 8, total the daily counts, traverse the rows and columns of the two-dimensional structure, and print the results. Traversal means visiting the elements in a structure, usually to inspect or process each one.

Java — arrays

1  public class RepairTotals {
2      public static void main(String[] args) {
3          int[] daily = {4, 7, 5};
4          int[][] repairs = {{2, 3, 4}, {5, 6, 7}};
5          daily[1] = 8;
6          int dailyTotal = 0;
7          for (int count : daily) {
8              dailyTotal += count;
9          }
10         int gridTotal = 0;
11         for (int row = 0; row < repairs.length; row++) {
12             for (int column = 0; column < repairs[row].length; column++) {
13                 gridTotal += repairs[row][column];
14             }
15         }
16         System.out.println(dailyTotal);
17         System.out.println(gridTotal);
18         System.out.println(repairs[1][2]);
19     }
20 }

Python — lists

1  daily = [4, 7, 5]
2  repairs = [[2, 3, 4], [5, 6, 7]]
3  daily[1] = 8
4  daily_total = 0
5  for count in daily:
6      daily_total += count
7  grid_total = 0
8  for row in range(len(repairs)):
9      for column in range(len(repairs[row])):
10         grid_total += repairs[row][column]
11 print(daily_total)
12 print(grid_total)
13 print(repairs[1][2])

The outer loop selects a row; the inner loop visits every column in that row. Using the current row's length keeps the column index within that row's bounds.

The trace below uses the Java variable names; Python's daily_total and grid_total hold the same values. A dash means that the variable has not yet been initialized or is not active at that stage.

EventdailycountdailyTotalrowcolumngridTotalOutput
Change middle count[4, 8, 5]——————
Initialize daily total[4, 8, 5]—0————
Process first count[4, 8, 5]44————
Process second count[4, 8, 5]812————
Process third count[4, 8, 5]517————
Initialize grid total[4, 8, 5]—17——0—
Process row 0, column 0[4, 8, 5]—17002—
Process row 0, column 1[4, 8, 5]—17015—
Process row 0, column 2[4, 8, 5]—17029—
Process row 1, column 0[4, 8, 5]—171014—
Process row 1, column 1[4, 8, 5]—171120—
Process row 1, column 2[4, 8, 5]—171227—
Print daily total[4, 8, 5]—17——2717
Print grid total[4, 8, 5]—17——2727
Print selected element[4, 8, 5]—17——277

Adding, removing and traversing a dynamic list

The next program starts with repair counts 4 and 7, adds 5 at the end, removes the element at index 1, then traverses the remaining elements to calculate a total. The figure tracks the list's contents and the index shift after removal.

Java — ArrayList

1  import java.util.ArrayList;
2  public class RepairList {
3      public static void main(String[] args) {
4          ArrayList<Integer> counts = new ArrayList<>();
5          counts.add(4);
6          counts.add(7);
7          counts.add(5);
8          counts.remove(1);
9          int total = 0;
10         for (int count : counts) {
11             total += count;
12         }
13         System.out.println(counts.size());
14         System.out.println(total);
15     }
16 }

Python — list

1  counts = [4, 7]
2  counts.append(5)
3  counts.pop(1)
4  total = 0
5  for count in counts:
6      total += count
7  print(len(counts))
8  print(total)
EventcountscounttotalOutput
Initial elements stored[4, 7]———
Add 5[4, 7, 5]———
Remove index 1[4, 5]———
Initialize total[4, 5]—0—
Process first element[4, 5]44—
Process second element[4, 5]59—
Print size[4, 5]—92
Print total[4, 5]—99

Removing index 1 removes the value 7. The value 5 then moves from index 2 to index 1, so the remaining elements still occupy consecutive indices.

Language-specific operations

PurposeJava arrayJava ArrayListPython list
Number of elementslength propertysize() methodlen() function
Read an elementSquare-bracket indexingget() methodSquare-bracket indexing
Replace an elementAssignment to an indexed positionset() methodAssignment to an indexed position
Add at the endNo automatic growthadd() methodappend() method
Remove by indexNo automatic shrinkingremove() with an integer indexpop() with an index

A Java ArrayList holding integer values uses Integer as its element type. In Java, an integer argument to remove() selects an index; in Python, remove() searches for a matching value, whereas pop() selects an index. Do not confuse removing by position with removing by value.

Key terms: one-dimensional (1D) arrays, two-dimensional (2D) arrays, ArrayLists, Lists, add, remove, traverse.

B2.2.3

STACKS AND LAST-IN, FIRST-OUT PROCESSING

Explain a stack by identifying the end where operations take place and showing why the last item added is removed first. Link this behaviour to a problem, then to the storage used to implement the stack.

One accessible end

A stack is a last-in, first-out (LIFO) data structure: the last item added is the next one removed. Items are added and removed at the same end, called the top.

LIFO tells us the order of access, not how the stack is implemented. The storage may have a fixed capacity or be able to grow.

Fundamental operations

OperationEffectChanges the stack?
pushAdds an item at the top.Yes: size increases by one.
popRemoves and returns the top item.Yes: size decreases by one.
peekReturns the top item without removing it.No.
isEmptyReturns true if the stack contains no items; otherwise returns false.No.

Both pop and peek require a non-empty stack. The program should check for this or handle the empty-stack case: an empty stack has no valid top item to return. It must also reject or handle a push to a full fixed-capacity stack.

Worked sequence: an undo history

A drawing program can store actions in a stack so that the latest action is undone first. Suppose a user adds a line, then a circle. In the figure, the circle is at the top. Undoing once removes the circle action, leaving the line action.

The trace below lists the stack contents from bottom to top. Each stored action must contain enough information for the program to reverse its effects. The stack controls the order; it doesn't perform the reversal itself.

OperationReturned valueStack after operation, bottom → top
isEmptytrueempty
Push line action—line action
Push circle action—line action, circle action
peekcircle actionline action, circle action
popcircle actionline action
isEmptyfalseline action
popline actionempty
isEmptytrueempty

A stack fits this problem because each undo must reverse the most recent remaining action. If the oldest action had to be processed first, a stack would not be appropriate.

Performance and memory usage

For an array-backed stack, the program keeps track of the top position. A normal push writes to the next available position. A pop reads the top item and moves the top position back. Neither operation has to shift all the other elements, while peek and isEmpty also take only a fixed amount of work.

A push that exceeds the backing array's capacity takes more work. A dynamic implementation must allocate larger storage and copy the existing entries into it. That individual push is slower, although most pushes do not resize the storage.

Fixed-capacity implementations reserve storage in advance, including positions that remain unused. A dynamic implementation can grow with the history, though it may keep spare capacity. Popping reduces the number of stored items without necessarily shrinking the allocated capacity immediately. Once the reference to a popped object is removed, its memory can become reclaimable if nothing else refers to it.

Keeping every action indefinitely can use substantial memory. A limit on the undo history saves storage, but users cannot undo actions beyond that limit.

Key terms: stack, last in, first out (LIFO), push, pop, peek, isEmpty, performance, memory usage.

B2.2.4

QUEUES AND FIRST-IN, FIRST-OUT PROCESSING

To explain a queue, distinguish the end where items enter from the end where they leave. Show why the earliest item still waiting is removed first. Choosing a queue involves both its service order and how it is implemented.

Two different ends

A queue is a first-in, first-out (FIFO) data structure: the earliest item still waiting is removed next. New items join at the rear; items leave from the front.

FIFO keeps items in arrival order. A stack gives the most recently added item the next turn; a queue does not.

Fundamental operations

OperationEffectChanges the queue?
enqueueAdds an item at the rear.Yes: size increases by one.
dequeueRemoves and returns the front item.Yes: size decreases by one.
frontReturns the front item without removing it.No.
isEmptyReturns true if the queue contains no items; otherwise returns false.No.

Here, front names both the inspection operation and the end where items are removed. The queue must be non-empty before calling front or dequeue. For a fixed-capacity queue, the implementation must also handle an enqueue attempt when no space remains.

Worked sequence: processing print jobs

A shared workshop printer can use a queue to process jobs in arrival order. If job A arrives before job B, job A sits at the front and is selected first. Once job A has been removed and job C arrives, job B is still ahead of job C, as shown below.

The trace lists the contents from front to rear.

OperationReturned valueQueue after operation, front → rear
isEmptytrueempty
Enqueue job A—job A
Enqueue job B—job A, job B
frontjob Ajob A, job B
dequeuejob Ajob B
Enqueue job C—job B, job C
dequeuejob Bjob C
isEmptyfalsejob C
dequeuejob Cempty
isEmptytrueempty

Use a queue when arrival order should determine service order. There is a limitation: an urgent new job must still wait behind earlier jobs. Ordinary FIFO behaviour does not select jobs by urgency.

Performance depends on implementation

In a simple array-backed implementation, removing the front element may involve shifting every remaining element towards the beginning. The longer the queue, the more work a dequeue takes. Removing index 0 from a Python list or Java ArrayList incurs this shifting cost.

Tracking the front and rear positions avoids moving all the waiting items after each dequeue. The positions can wrap around the allocated storage, allowing vacated positions to be reused. When space is available, enqueue and dequeue then take only a fixed amount of work. Tracking the front position and current size also lets front and isEmpty use only a fixed amount of work.

A dynamic implementation may need to enlarge its backing storage. Allocation and copying make that particular enqueue slower. FIFO alone doesn't guarantee the same performance across implementations.

Memory usage

Enqueue increases the number of stored items; dequeue decreases it. Calling front or isEmpty leaves the queue's contents unchanged.

A fixed-capacity queue reserves predictable storage, but it can become full. A dynamic queue accommodates a changing workload at the cost of spare capacity and possible resizing. Dequeuing does not necessarily reduce the allocated capacity immediately. References to removed items should no longer be retained unnecessarily.

If jobs arrive faster than the printer processes them, the waiting queue grows. More storage can hold a larger backlog. It won't make the printer process jobs faster.

Key terms: queue, first in, first out (FIFO), enqueue, dequeue, front, isEmpty, performance, memory usage.

Were those notes helpful?

B2.1 Programming fundamentals

B2.3 Programming constructs