DSM-103 Data Structures · full course recap · S01 → 21 Sep (Dey, Prakash)

array / contiguous (index arithmetic) linked / pointer (follow the link)

S01 (18 Aug) · S02 (20 Aug) — Dey

Foundations and the 1-D array

The vocabulary, the address formula, and the first three array operations — all written with L and U, never a language's own indexing.

VOCABULARY

data vs information
34 is data; "age = 34" is information. A value only means something once it is tied to an attribute of an entity.
ADT
Logical description: a collection of data plus the operations on it, with no word about implementation. E.g. {5, 12, 16, 7, 10} with max().
data structure
The implementation of an ADT: its physical form, built from primitives (int float char double).

THREE WAYS TO CLASSIFY

linear | non-linear
array, linked list, stack, queue | tree, graph, heap, trie (jump through links; e.g. a family tree)
homogeneous | non-homogeneous
array: one type only | struct / record: name (string) + age (int) + salary (float). Types are laid out differently in memory: int 5 → 101 with a sign bit; float 5.5 = 55×10⁻¹ → sign | exponent | mantissa; char → an unsigned code. So a slot built for one type can't hold another. (Python's mixed lists hide this in library code.)
static | dynamic
array: fixed block L…U, contiguous; free space elsewhere is useless to it | linked list: grabs scattered free blocks and links them

1-D ARRAY FORMULAS

array
a finite, ordered collection of homogeneous elements in contiguous memory
size
$\text{Size} = U - L + 1$
address (mapping function)
$\text{Addr}(A[I]) = M + (I - L)\times W$
M = base address, W = bytes per element. Special case L = 1, W = 1: $M + (I-1)$.
I-th element
sits at index $L + I - 1$

THE THREE OPERATIONS (20 AUG) — state input, output, data structure first

  • Traverse — I ← L; while I ≤ U: process A[I]; I ← I+1. O(n) time, O(1) space.
  • Bubble sort — compare only neighbours, swap when out of order; each pass parks the largest at I, then I ← I−1. Inner loop is J < I, not ≤, or A[J+1] doesn't exist. O(n²) time, O(1) space. Flip the test for descending.
  • Linear search — FOUND flag + LOCATION (start at 0, or a negative number if 0 is a valid index). Loop ends for two reasons, so test FOUND after it. Best O(1), worst O(n).

LOOPS AND INDEXING

  • while / for check the condition first; do-while runs the body once before checking. With I = 5 and condition 5 < 4: while → 0 runs, do-while → 1 run.
  • 0-based: C, Python, Java. 1-based: R vectors, SQL SUBSTRING. So write algorithms with L/U and let the formula absorb the difference.
  • Selection sort (mentioned): compare one element with all the others. Bubble sort only ever compares adjacent pairs.

Why a data scientist needs this (S01):

  • Store, manipulate, answer queries fast: a self-driving car's decision that arrives after a minute is useless.
  • Stock ticker: the highest-value stock must already be sitting at the front. If you had to scan for it, another stock would have overtaken it by the time you got there. That's why heaps and priority structures exist.
  • Printer: 10 jobs come out in firing order (a queue). Add authority levels and the boss's job jumps ahead (a priority queue).
  • Follow / follower relationships are a graph. A TF-IDF matrix is symmetric, so storing both triangles is redundant (hence module 2).
  • "No single storage structure is good for all applications": judge every choice on time and space, and expect to trade one for the other.
Bubble sort, one comparison at a time

The amber frame is the pair being compared (J, J+1). The teal zone is the settled part to the right of I. Watch the largest value ride to the end of each pass.

← → step · space play/pause (click the panel first)

WORKED EXAMPLES

M = 1000, L = 1, U = 100, one element = 1 byte. Address of the 4th element?S01 18 Aug
  1. $\text{Addr}(A[4]) = 1000 + (4-1)\times 1$
  2. Check by layout: A[1]→1000, A[2]→1001, A[3]→1002, A[4]→1003.

1003

Same base 1000, but each element takes 2 bytes. Where does A[2] start?S01 18 Aug
  1. A[1] occupies 1000–1001, so the next free byte is 1002.
  2. Formula: $1000 + (2-1)\times 2 = 1002$.
  3. Same idea on 20 Aug: first element at 453 with 2-byte elements → the second is at 455.

1002

Homework: make linear search return the last occurrence of KEY, then all occurrences.S02 20 Aug assignment
  1. Route 1: scan backwards. Start I ← U, decrement, and stop at the first match. That first match from the right is the last occurrence.
  2. Route 2: drop the early exit. Remove FOUND = 0 from the loop test and overwrite LOCATION ← I on every match. You always scan all n elements.
  3. Worst case is the same for both: a single match at the very end still costs a full scan.
  4. All occurrences: keep an auxiliary flag array the same size as A and set a 1 at every matching index. That costs O(n) extra space, which is the trade-off the class discussed. A counter gives the frequency.

last: I ← U, decrement · all: flag array, O(n) space

Why must the bubble-sort inner loop be J < I and not J ≤ I?S02 20 Aug
  1. J names the pair (A[J], A[J+1]).
  2. If J reached I = U, the step would read A[U+1], an element that doesn't exist.
  3. The outer I also stops the inner loop from re-touching the settled tail.

so that A[J+1] always exists

PRACTICE

An array is indexed 5…24, base M = 2000, W = 4 bytes. Find its size and the address of A[12].practice
  1. $\text{Size} = 24 - 5 + 1 = 20$
  2. $\text{Addr} = 2000 + (12-5)\times 4 = 2028$

20 elements · 2028

Bubble sort on 4 3 2 1: how many comparisons and swaps? (Type it into the widget to watch.)practice
  1. Passes compare 3, 2 and 1 pairs: 6 comparisons in total.
  2. The input is in reverse order, so every comparison fails and swaps. This is the worst case.
  3. In general that's $\frac{n(n-1)}{2}$ comparisons, which is O(n²).

6 comparisons, 6 swaps

I = 5; do { print I; I = I + 1 } while (I < 4) — what prints?practice
  1. do-while runs the body before it checks anything, so it prints 5.
  2. Then it checks 6 < 4, which is false, and stops. A while loop would have printed nothing.

5 (once)

S03 (22 Aug) — Dey · S06 said exams ask indexing calculations

2-D arrays and sparse (triangular, band) matrices

Memory is a single line, so a grid has to be flattened. Every formula here comes from one method: count what is stored before the element.

LOGICAL vs PHYSICAL

logical
$a_{ij}$ = row $i$, column $j$ of an $m\times n$ grid (two subscripts). 1-based throughout.
physical
contiguous 1-D memory → a mapping formula turns $(i,j)$ into one index
row-major
row 1, then row 2, … — the compiler default
column-major
column 1, then column 2, … — you write this conversion yourself

THE ONE METHOD

  • Find how many elements each row (or column) stores.
  • Add up the complete rows (or columns) before $a_{ij}$.
  • Add $a_{ij}$'s position inside its own row or column, counted from that row's first stored cell, not from column 1.
  • That sum is the index. Then $\text{Addr} = B + (\text{Index}-1)\,w$.
  • Costs one multiply plus some additions, so O(1) per access.

FULL m × n

row-major
$\text{Index} = (i-1)\,n + j$
$\text{Addr} = B + [(i-1)n + (j-1)]\,w$
column-major
$\text{Index} = (j-1)\,m + i$
$\text{Addr} = B + [(j-1)m + (i-1)]\,w$
index ≠ address
5 is a position. The address is $B + (5-1)w$. Class kept mixing these up.
matrix (n × n, 1-based)stored per rowrow-major index of $a_{ij}$column-major index of $a_{ij}$
lower-left triangular ($j \le i$)row $i$ has $i$$\frac{i(i-1)}{2} + j$$n(j-1) - \frac{(j-1)(j-2)}{2} + (i-j+1)$
upper-right triangular ($j \ge i$)row $i$ has $n-i+1$$n(i-1) - \frac{(i-1)(i-2)}{2} + (j-i+1)$$\frac{j(j-1)}{2} + i$
diagonal ($i = j$)1$i$  (both orders agree)
tridiagonal ($|i-j| \le 1$)2, 3, 3, …, 3, 2$2i + j - 2$$2j + i - 2$
α–β band, case 1: $i \le \beta$$\alpha + i - 1$ (growing)$(i-1)\alpha + \frac{(i-1)(i-2)}{2} + j$
α–β band, case 2: $\beta < i \le n-\alpha+1$$\alpha+\beta-1$ (full)$\beta\alpha + \frac{\beta(\beta-1)}{2} + (i-\beta-1)(\alpha+\beta-1) + (j-i+\beta)$
α–β band, case 3: $i > n-\alpha+1$shrinkingcase-2 prefix up to row $n-\alpha+1$ $+ \sum_{k=n-\alpha+2}^{i-1}(n-k+\beta) + (j-i+\beta)$

Upper-right is lower-left with the roles of i and j swapped ("i will be j, j will be i"). Band: α = cells in row 1 (the diagonal plus everything to its right), β = cells in column 1 (the diagonal plus everything below). The case formulas need α + β ≤ n + 1. All formulas above were checked against a brute-force count for every n up to 9.

Why bother (S03):

  • A 100 × 100 array with only about 500 real values wastes about 9,500 cells. A 10,000 × 10,000 array at 8 bytes per element is 8×10⁸ bytes. Store just the band.
  • The price is nothing: the index formula is O(1).
  • This mattered most in the 512 MB RAM / 20 GB disk days. Today it still applies to band-shaped data, such as the matrices in PCA.
  • "You must practice this α–β band matrix." α and β are different parameters, so don't collapse them into one. Four triangle shapes exist: upper-left, upper-right, lower-left, lower-right.
Index calculator: watch memory fill up

Click any cell to make it the target, then press Count it. Cells light up in storage order while the 1-D strip below fills in. The breakdown shows the lecture formula term by term.

WORKED EXAMPLES

3 × 4 array, B = 100, w = 1. Index and address of $a_{23}$ in row-major, then column-major.S03
  1. Row-major: $(2-1)\times4 + 3 = 7$, so $\text{Addr} = 100 + (7-1)\times1 = 106$.
  2. Column-major ($m = 3$): $(3-1)\times3 + 2 = 8$, so $\text{Addr} = B + 7$.

row-major 7 (address 106) · column-major 8

Lower-left triangular 3 × 3: index of $a_{32}$ in both orders.S03
  1. Row-major: $\frac{3\cdot2}{2} + 2 = 5$. Layout: a11 | a21 a22 | a31 a32 …
  2. Column-major: $3(2-1) - 0 + (3-2+1) = 5$. Layout: a11 a21 a31 | a22 a32 a33.
  3. Edge case: at $j = 1$ nothing comes before, so the index is just $i$. A column "j = 0" doesn't exist; it isn't a mistake in the formula.

5 in both orders (the same number by coincidence)

α–β band: n = 15, α = 3, β = 4. Index of $a_{10,11}$.S03 — practise this one
  1. Which case? $\beta = 4 < i = 10 \le n-\alpha+1 = 13$, so case 2.
  2. Rows 1–4 (growing): $\beta\alpha + \frac{\beta(\beta-1)}{2} = 12 + 6 = 18$.
  3. Rows 5–9: there are $(10-4-1) = 5$ rows ("last − first + 1"), each $3+4-1 = 6$ wide, giving 30.
  4. Inside row 10: $j - i + \beta = 11 - 10 + 4 = 5$.

18 + 30 + 5 = 53 → Address = B + 52·w

PRACTICE

Upper-right triangular, n = 5, row-major, B = 500, w = 4. Index and address of $a_{24}$.practice
  1. Rows before: $5\cdot1 - 0 = 5$ (row 1 is full).
  2. Row 2 starts at column 2, so $a_{24}$ is at position $4 - 2 + 1 = 3$.
  3. Index 8, so $\text{Addr} = 500 + 7\cdot4$.

Index 8 · Address 528

Band n = 10, α = 2, β = 3: index of $a_{9,10}$. Then α = 3, β = 2: index of $a_{10,10}$.practice · cases 2 and 3
  1. α=2, β=3: $n-\alpha+1 = 9$ and $i = 9$, so case 2. $6 + 3 = 9$; rows 4–8 are 5 rows × 4 = 20; position $10-9+3 = 4$. Total 33.
  2. α=3, β=2: $n-\alpha+1 = 8 < 10$, so case 3. $6 + 1 = 7$; rows 3–8 are 6 × 4 = 24; shrinking row 9 holds $10 - 9 + 2 = 3$; position $10 - 10 + 2 = 2$. Total 36.
  3. Put both into the calculator to watch the count.

33 · 36

Homework: derive both orders for the lower-right triangle (row 1 has one cell, at column n) and the upper-left triangle (row 1 full, row n has only $a_{n1}$).S03 homeworkderived here, checked by brute force
  1. Lower-right ($i + j \ge n+1$): row $i$ holds $i$ cells, starting at column $n-i+1$. Row-major: $\frac{i(i-1)}{2} + (j - n + i)$. Column $j$ holds $j$ cells, starting at row $n-j+1$. Column-major: $\frac{j(j-1)}{2} + (i - n + j)$.
  2. Upper-left ($i + j \le n+1$): row $i$ holds $n-i+1$ cells, starting at column 1. Row-major: $n(i-1) - \frac{(i-1)(i-2)}{2} + j$. Column-major swaps $i$ and $j$: $n(j-1) - \frac{(j-1)(j-2)}{2} + i$.
  3. Check with n = 4: lower-right $a_{33}$ gives 5 in both orders. Upper-left $a_{23}$ gives 7 (row-major) and 9 (column-major).

same method: stored-before + offset from the row's first stored cell

S04 (29 Aug) · S05 (3 Sep) — Dey · S06 said exams ask you to fill in missing algorithm steps

Linked lists: singly and circular

Data goes wherever memory is free, and each node remembers where the next one is. Almost every bug comes from one of three things: where the pointer starts, which test ends the loop, or the order of two assignments.

NODE, HEADER, NULL

linked list
an ordered, finite collection of homogeneous nodes, whose linear order is kept by links
node
[data | link], where link = the address of the next node. Every node has the same fields.
header
same node type, data unused, link → first real node. It always exists, even when the list is empty.
NULL
the last node's link. It is the stop signal: without it you would read into memory you were never given.
no indexing
"node i" doesn't exist. You always walk from the header.

TWO REPRESENTATIONS

static
two parallel arrays of the same size: DATA[k] holds the value, LINK[k] holds the index of the next one. Fixed size.
dynamic
real pointers. getNode() returns a fresh node's address, or NULL when memory is full (overflow). With an 8 B int and an 8 B pointer, one node is 16 B.
ptr->data · ptr->link
the value · the address of the next node
GETNODE / RETURNNODE
take a block from the heap's free list / give it back. Memory is never "removed".

POINTER DISCIPLINE (the traps)

  • ptr = header + while ptr->link ≠ NULL stops on the last node and is safe on an empty list. Use it for insert at end, delete last, and concatenate.
  • ptr = header->link + while ptr ≠ NULL visits every node. Use it for traverse, search and copy.
  • Link the new node first, then redirect the old arrow, or you lose the rest of the list.
  • Deleting needs the predecessor, so use two pointers: ptr and ptr1.
  • A loop that can end for two reasons must be followed by a test that tells them apart: ptr->data = key.
  • Check getNode() = NULL before inserting, and check for an empty list before deleting.

SLL → CIRCULAR SLL (last node points back to the header)

  • Rule: every end-of-list NULL becomes header. So ptr ≠ header, ptr->link ≠ header, new->link ← header. Empty: header->link = header.
  • Insert at front: no change. It never looks for the end.
  • Insert at end: new->link ← header plus the loop test.
  • Insert after key: the mechanical conversion if ptr->link = header is wrong when the key is last. Test ptr->data ≠ key instead.
  • Delete first: only the underflow test changes. Delete last: three changes (underflow test, loop test, ptr1->link ← header). Delete key: two lines.

MORE OPERATIONS (S05)

  • Copy: a new header, then for each node getNode a fresh node, copy the data and hang it on ptr1. Two independent lists, with no arrow between them. O(n) time and O(n) space.
  • Concatenate: ptr = header1, walk to the last node, ptr->link = header2->link, then header2->link = NULL before RETURNNODE(header2).
  • Search: flag plus a location pointer. Loop while ptr ≠ NULL and flag = 0. The NULL test comes before any ptr->data, so empty lists and missing keys are safe.
singly linked listtimewhy
insert at front · delete firstO(1)a fixed handful of statements, no loop
insert at end · delete lastO(n)walk to the last node (and its predecessor)
insert after key · delete key · searchO(n)best O(1) when the key is first; quote the worst case
traverse · copyO(n)every node once. Copy also uses O(n) extra space.
concatenate (lists of m and k nodes)O(m)only list 1 is walked

Why linked lists exist (S04, S05):

  • Old machines often had enough free memory in total, but not in one contiguous block. So store each item wherever there is room and remember where the next one is.
  • Python's list "does this for you": you never manage the pointers. (Strictly, CPython's list is a resizable array of pointers, which is why indexing it is O(1). The professor's point is that Python manages the memory for you.)
  • S05 was taught almost entirely through questions: "I have not taught anything here, only asked questions." Expect exam questions in the same style: find the line that breaks.
Pointer surgery, one statement at a time

Nodes are [data | link]. An amber arrow is the link the current line just changed. Dashed nodes are unreachable. The labels above the nodes are the pointer variables.

← → step · space play/pause · the drawing scrolls sideways on a phone

WORKED EXAMPLES

Static representation: follow the list starting at index 109.S04
index : 109  105  101  125  113
DATA  :  20   30   25   35   …
LINK  : 105  101  125  113  NULL
  1. 109 gives 20, then go to 105. 105 gives 30, then 101. 101 gives 25, then 125. 125 gives 35, then 113. 113's link is NULL, so stop.
  2. Rule: the DATA array at a position holds the value, and the same position in the LINK array holds the next index.

20 → 30 → 25 → 35 → (113) → NULL

Copy a list. What three constraints did the class insist on?S05
  1. The original list is not modified.
  2. The copy gets its own header, because a list can't exist without one.
  3. The two lists stay completely separate: a fresh GETNODE for every element, never a copied pointer. ptr walks the original while ptr1 builds the copy in step; header1 itself never moves, because it is what gets returned.

own header · fresh node per element · O(n) time and space

Search: why is while (ptr ≠ NULL) AND (flag = 0) safe on an empty list and for a missing key?S05
  1. Empty list: ptr = header->link = NULL, so the test fails immediately and ptr->data is never read.
  2. Missing key (e.g. 45): ptr steps until it becomes NULL. The loop test runs before the data is examined, so nothing illegal is read.
  3. After the loop, flag tells you which of the two exits happened, and location holds the node's address or NULL.

the NULL check guards every dereference

PRACTICE

Assignment 2: in a circular list, delete a key using the merged loop while (ptr ≠ header) AND (ptr.data ≠ key). Does it work for the key first, in the middle, last, and absent?S05 homeworkworked out here
  1. Start with ptr ← header.link and ptr1 ← header. The loop body is ptr1 ← ptr; ptr ← ptr.link.
  2. First: the loop doesn't run, ptr1 = header, and ptr1.link ← ptr.link unlinks it. ✓
  3. Middle: it stops with ptr on the key and ptr1 on the predecessor. ✓
  4. Last: it stops on the last node, and ptr1.link ← ptr.link copies header, so the circle stays closed. ✓
  5. Absent (or empty list): the loop ends with ptr = header. Deleting now would unlink the header. ✗ Modification: add if ptr = header then "key not found" before deleting.

works for first, middle and last · needs an "if ptr = header" check for a missing key

A student proposes if (ptr.link ≠ key) for the insert-after-key fix. Why was it rejected?S05
  1. ptr.link is an address and key is a data value.
  2. Comparing them is meaningless. The fix has to be ptr.data ≠ key.

you can't compare an address with a value

S06 (5 Sep) — Dey

Doubly linked lists and sparse matrices as multi-linked lists

Adding a second link per node lets you walk backwards. Adding a header link to the last node turns "insert at end" from O(n) into O(1).

DOUBLY LINKED (DLL)

node
[LLINK | DATA | RLINK]: back to the previous node, forward to the next. The order you declare the fields in doesn't matter.
linear DLL
header LLINK = NULL · header RLINK → first node · last node's RLINK = NULL
traverse
one pointer: PTR = PTR->RLINK forwards, PTR = PTR->LLINK backwards. O(n).
insert at front
4 links: NEW→R, NEW→L, old first's LLINK, HEADER->RLINK. Fix the old first node's LLINK before overwriting HEADER->RLINK. O(1).

CIRCULAR DOUBLY LINKED (CDLL)

shape
last node's RLINK → header, and HEADER->LLINK → last node
payoff
insert at end: PTR = HEADER->LLINK reaches the last node with no walk. O(n) becomes O(1).
loop test
PTR ≠ HEADER instead of PTR ≠ NULL
empty-list trap
on an empty list PTR = HEADER->RLINK = NULL, and PTR->LLINK = NEW then crashes. Give the empty case its own branch.

DLL delete, search and insert at an arbitrary position were set as self-study. They use the SLL logic plus the left links.

SPARSE MATRIX, MULTI-LINKED

node (5 fields)
[i | j | DATA | ROW LINK | COL LINK]: row link → next non-zero in the same row, col link → next non-zero in the same column
row / column headers
same structure. A zero in i or j marks a header. A row header's DATA holds that row's count of non-zeros. Its ROW LINK → the row's first element, its COL LINK → the next row header.
main header
stores the number of rows and columns. Its links point to RH₁ and CH₁.
cost
reaching $(i,j)$ means walking at most $n + n$ steps, so O(n), against O(1) for an array. Avoids the O(n²) full scan.

Where linked lists show up (S06):

  • Spotify playlist: "previous song" needs a backward link, so use a DLL. "Play in the order added" is a queue, which is an ADT: a DLL can implement it.
  • Insert 3 into 1 2 4 5 6 7 8: an array shifts every later element one place right, while a list just re-links two pointers. This is the linked list's key advantage.
  • Polynomials: nodes of [coefficient | exponent | link]. Adding or multiplying polynomials just inserts result terms as new nodes.
  • Free-space management: the OS chains its scattered free blocks into a linked list.
  • Four pointers per node (so you can also go up and left) is possible, but "a nightmare" to manage. A Sudoku grid fills every cell, so it isn't sparse: use a plain 2-D array.
  • The online exam's questions come "one after another and you cannot go back, just like a singly linked list".
Doubly linked: forward and back arrows

Upper arrows are RLINK (forward) and lower arrows are LLINK (back). Compare the two "insert at end" presets: one walks every node, the other jumps straight to the end.

WORKED EXAMPLES

Header at 100, NEW at 200, old first node (20) at 300. After inserting at the front, what does each link field store?S06
  1. NEW->RLINK = 300 (forward to 20). NEW->LLINK = 100 (back to the header).
  2. PTR->LLINK (node 20's back link) = 200, the address of NEW itself, not "NEW->RLINK".
  3. HEADER->RLINK = 200.

300 · 100 · 200 · 200

A 10,000 × 10,000 matrix has 100 non-zeros, with 8-byte values and 8-byte addresses. Dense array vs linked storage?S06
  1. Dense: $10^4 \times 10^4 \times 8 = 8\times10^8$ bytes. The useful data is only $100 \times 8 = 800$ bytes.
  2. Linked, as first sketched (data + 2 addresses): $8\times3 = 24$ bytes per node, and $24\times100 = 2400$ bytes.
  3. Trade-off: you lose O(1) direct access, because you walk along rows and columns instead.

8×10⁸ B vs 2,400 B

PRACTICE

Draw $5x^5 + 3x^3 + x + 7$ as a linked list.practice
  1. One node per non-zero term, as [coef | exp | link], in decreasing exponent.
  2. The missing $x^4$ and $x^2$ terms simply have no node.

[5|5] → [3|3] → [1|1] → [7|0] → NULL

Same 100 non-zeros, but with the full 5-field node (i, j, data, row link, column link), each field 8 bytes.practice
  1. $5 \times 8 = 40$ bytes per node, and $40 \times 100 = 4000$ bytes.
  2. That's still about 200,000× smaller than the dense array. The row, column and main header nodes add some overhead on top.

4,000 B for the element nodes

S07 (14 Sep) · S08 (17 Sep) — Prakash

Stacks and arithmetic expressions

A stack is an array or a linked list with one rule added: insert and delete at one end only. That rule is enough to reverse strings and evaluate expressions.

DEFINE ANY DATA STRUCTURE BY…

  • what data is stored (int, char, text…)
  • how it is arranged: linear (before/after) or non-linear (parent/child)
  • how it may be changed: insert, delete, update. This third rule is what makes a stack a stack rather than just an array.
stack
an ordered collection of homogeneous elements with insertion and deletion at one end only. LIFO: last in, first out.

ARRAY STACK (S08: 0-based, top = −1)

push(x)
full? then OVERFLOW · top ← top+1 then A[top] ← x
pop()
empty? then UNDERFLOW · x ← A[top] then top ← top−1
isEmpty · isFull
top = −1 · top = SIZE−1. With general bounds L…U (S07): empty is top < L, full is top ≥ U.
why −1
it's an impossible index, and −1 + 1 = 0, so the first push needs no special case
status (S07)
free % $= \frac{\text{size}-\text{top}}{\text{size}}\times100$

LINKED STACK

push
new←GETNODE(); new->data←item; new->link←top; top←new; stackhead->link←top
pop
top = NULL? then empty · item←top->data; ptr←top->link; top←ptr; stackhead->link←ptr
full?
meaningless, since memory is dynamic. status becomes a count of the nodes (O(n) traversal).
the variable new
only a local copy of an address. The node survives because top now holds that address.

NOTATIONS · PRECEDENCE

infix · prefix · postfix
a + b · + a b · a b +. Same value; only the position of the operator changes. Unary operators (++a, a++) have no infix form.
why postfix
(1) a stack evaluates it directly; (2) it needs no parentheses. Humans write infix, and the machine converts it, just as a compiler translates code.
precedence
^ > * / > + −. Brackets first, innermost first.
associativity
^ is right-to-left (a^b^c = a^(b^c)). * / + − are left-to-right.

POSTFIX EVALUATION (the rule to remember)

  • Operand: push. Operator: arg2 = pop(), arg1 = pop(), push(arg1 op arg2).
  • "Remember, this is important": the first pop is arg2 (the right operand). It doesn't matter for + and ×, but it decides the answer for − and ÷.
  • At the end exactly one value is left, and that is the answer. A pop from an empty stack means the expression was malformed (underflow).
  • Overflow goes with push, underflow goes with pop.

Analogies (S07, S08):

  • A stack of trays or chairs: to put one in the middle you would have to lift off the top three first. That's why a stack forbids it.
  • Where stacks appear: undo/redo, nested function calls (f₁ calls f₂ calls f₃…), parenthesis matching (both the count and the nesting), backtracking, syntax parsing, and string reversal (palindromes).
  • Multi-digit numbers on paper: 1 2 23 could be misread. Leave space or bracket them, e.g. 1 2 (23). Inside the machine each number is a separate token, so there's no ambiguity.
The stack as a bucket: one open end

The indices 0…3 run up the side of the bucket. Watch top move before a push stores and after a pop reads. In postfix mode, the two popped values move out to arg1 op arg2, and the result drops back in.

Try a broken one, like 3 + (underflow) or 1 2 3 + (left-over operand). Tokens are separated by spaces.

WORKED EXAMPLES

Convert (2*3)+4 and 2*(3+4) to postfix.S08
  1. 2*3 gives 2 3 *. Then (2 3 *) + 4 gives 2 3 * 4 +, which is 10.
  2. 3+4 gives 3 4 +. Then 2 * (3 4 +) gives 2 3 4 + *, which is 14.
  3. The two strings differ, and each one fixes its evaluation order without brackets.

2 3 * 4 + (=10) · 2 3 4 + * (=14)

A − (B − (C − D)) to postfix and prefix.S08
  1. Innermost first: C D −. Then B C D − −. Then A B C D − − −.
  2. Prefix, by the same steps with the operator written first: − C D, then − B − C D, then − A − B − C D.

postfix A B C D − − − · prefix − A − B − C D

a ^ b ^ c to prefix.S08 · associativity
  1. ^ is right-to-left, so b ^ c comes first: ^ b c.
  2. Then a ^ (^ b c) gives ^ a ^ b c.

^ a ^ b c

PRACTICE

Evaluate 8 2 − 3 /. What goes wrong if you swap arg1 and arg2? (It's preloaded in the input box above.)practice
  1. Push 8, push 2. At −: arg2 = 2, arg1 = 8, giving 8 − 2 = 6.
  2. Push 3. At /: arg2 = 3, arg1 = 6, giving 6 / 3 = 2.
  3. Swapped, you'd get 2 − 8 = −6 and then 3 / −6 = −0.5.

2 (swapped order gives −0.5)

Convert (A + B) * C − D / E to postfix.practice
  1. The bracket first: A B +.
  2. * and / before −: (A B +) C * and D E /.
  3. Then the −.

A B + C * D E / −

S09 (18 Sep) · S10 (19 Sep) — Prakash

Queues: linear, circular, linked

A queue uses two ends, each with one job: insert at the rear, delete at the front. The naive array version wastes the cells it has already served. Treating the array as a ring fixes that with pointer arithmetic alone.

QUEUE BASICS

queue
linear. Add at the rear (ENQUEUE, which takes a value), remove from the front (DEQUEUE, which takes none). FIFO: first in, first out. A stack is LIFO.
no update
you can't reach into the middle and change a value
growing restriction
array / list (insert or delete anywhere), then stack (one end), then queue (two ends, one job each)
pointers
stack: TOP · queue: FRONT and REAR, because the two jobs happen at different ends

ARRAY QUEUE Q[1…N] (S09)

empty
FRONT = REAR = 0. 0 is an impossible index, the same idea as the stack's −1.
one element · count
FRONT = REAR · REAR − FRONT + 1 (e.g. 5…10 holds 6). Linear only.
full
naive: REAR = N. True: FRONT = 1 AND REAR = N, which only makes sense with compaction.
two special cases (the exam trap)
enqueue into an empty queue also sets FRONT = 1. Dequeuing the last element (FRONT = REAR) resets both to 0.

CIRCULAR QUEUE (S10)

advance
$r = (r \bmod n) + 1$, $\;f = (f \bmod n) + 1$. When $r = n$ it gives 1; otherwise it is just $r+1$.
full
$(\text{rear} \bmod n) + 1 = \text{front}$: the rear sits right behind the front
empty
front = rear = 0. Deleting when front = rear resets both.
what changes
rear can be less than front, so $\text{R} - \text{F} + 1$ breaks. The capacity is still $n$: circularity adds utilisation, not space. Enqueue and dequeue are O(1).

THREE FIXES FOR THE FALSE "FULL"

  • 1 · Compact on full: when REAR = N but FRONT > 1, shift everything back to start at index 1, set REAR ← REAR − FRONT + 1 and FRONT ← 1, then insert. That costs O(N) whenever it happens.
  • 2 · Shift on every dequeue: FRONT stays at 1, so REAR = N becomes a true fullness test. Every dequeue costs O(N).
  • 3 · Circular: no shifting at all, so O(1). "We are just utilizing the space which is free. We are not doing anything extra to make the space free."

LINKED-LIST QUEUE

  • Enqueue (at the rear): new->link = NULL. If front = NULL, then front = rear = new and qhead->link = front. Otherwise rear->link = new; rear = new.
  • Dequeue (at the front): item = front->data; ptr = front->link; qhead->link = ptr; front = ptr. If front = NULL, also set rear = NULL.
  • No circular version is needed: nodes exist only for real items, so there are never idle slots. Both operations are O(1).
circular array queuelinked-list queue
memory reserveda fixed block of n slots up frontexactly one node per element
waste / capacity riskmay idle (100 slots, 10 used) or overflow (need 101)neither
space per elementbetter: data onlyworse: data + link
enqueue / dequeueO(1)O(1)
overflow · underflowinsert into a full queue · delete from an empty queue (same as a stack's push and pop)

Where queues appear (S09):

  • Toll booths, bank counters, grocery checkouts, traffic, and assembly lines: first come, first served.
  • In computing: an email server sends mail in arrival order. A multi-user server queues requests by arrival time. Printer spooling and access to memory, I/O and disk from several processes are all queues.
  • These are cases where you want neither an array's complete freedom nor a stack's single end. You want the order preserved.
Same operations, two queues

Both queues get identical operations. The data never moves: only F and R do. The ring is the circular queue's own cells 1…n bent into a circle, with the same indices. Step to the 6th operation in the first preset to see the difference.

+ enqueue / − dequeue add your own operation to the end of the sequence

WORKED EXAMPLES

N = 10, with Q[8] = 23 and Q[9] = 21 (FRONT 8, REAR 9). Trace DQ, NQ 24, NQ 27, DQ, DQ, DQ, NQ 22, NQ 28, DQ, DQ.S09
  1. DQ removes 23, F = 9. NQ 24 gives R = 10, Q[10] = 24.
  2. NQ 27: REAR = N, so "Queue is full", even though cells 1–8 are empty.
  3. DQ removes 21 (F = 10). DQ removes 24: F = R, so it was the last one, and F = R = 0. DQ: F = 0, so "Queue is empty".
  4. NQ 22: empty, so F = R = 1. NQ 28 gives R = 2. DQ removes 22 (F = 2). DQ removes 28, and F = R = 0.

the false "full" at step 3 is the drawback that motivates the circular queue

Circular, n = 4: enqueue A B C D, dequeue, enqueue E. What are front and rear now?S10 — asked in class
  1. After A–D: f = 1, r = 4. Dequeue removes A, and f = 2.
  2. Enqueue E: next = (4 mod 4) + 1 = 1 ≠ f, so r = 1 and CQ[1] = E.
  3. Now rear < front, which a linear queue can never have. A linear queue would have said "full" here, or shifted B, C, D left.

r = 1, f = 2

Homework: check a palindrome using a stack and a queue.S10 homework
PALINDROME(S)
  create empty stack ST and empty queue Q
  for each ch in S:  push(ST, ch);  enqueue(Q, ch)
  while ST not empty:
      if pop(ST) ≠ dequeue(Q):  return "not a palindrome"
  return "palindrome"
  1. The queue gives the characters back in their original order, and the stack gives them back reversed. If the two agree everywhere, the string is a palindrome.
  2. MOM: M = M, O = O, M = M, so it's a palindrome. MON: the first comparison is N vs M, which fails immediately, so there's no need to continue.

MOM ✓ · MON ✗ at the first comparison

PRACTICE

Circular, n = 5, front = 4, rear = 2. How many elements, and what happens on the next two enqueues?practice
  1. The occupied cells are 4, 5, 1, 2, so there are 4 elements. (A closed form: $(r - f + n) \bmod n + 1 = 4$.) beyond notes
  2. Enqueue: next = (2 mod 5) + 1 = 3 ≠ 4, so rear = 3. Now all 5 cells are used.
  3. Enqueue again: next = (3 mod 5) + 1 = 4 = front, so "Queue is full".

4 elements · the first enqueue fits at 3 · the second is refused (full)

Linear N = 5 with FRONT = 3, REAR = 5, using solution 1 (compaction). Enqueue X.practice
  1. REAR = N but FRONT = 3 > 1, so it isn't truly full. Compact: Q[3…5] moves to Q[1…3].
  2. REAR ← 5 − 3 + 1 = 3, FRONT ← 1. Then REAR = 4 and Q[4] = X.
  3. The shift cost 3 copies, and in the worst case it is O(N).

FRONT 1, REAR 4

S11 (21 Sep) — Prakash · plus the S06 primer (Dey)

Algorithms and Big-O

Count steps, not seconds. Then keep only how the count grows, because constants and small inputs don't matter.

ALGORITHM

definition
a sequence of unambiguous instructions that solves a problem, giving the required output for every legitimate input in finite time
vs program
an algorithm is language-independent (pseudocode). A program is one implementation of it in C, Pascal, and so on. One algorithm can have many programs.
five characteristics
uniqueness (each step has one meaning) · finiteness (it must stop) · precision · input/output (0 or more in, at least 1 out) · generality (works for every valid input)

MEASURING

why not seconds
5 s vs 10 s says nothing, because the CPU, disk, instruction set and compiler all differ. Count elementary steps, assuming each takes the same constant time.
T(n)
steps as a function of input size n. Time complexity and space complexity (memory) together make up computational complexity.
asymptotic
behaviour as $n \to \infty$. Adding 5 numbers costs nothing, but adding a million numbers matters.

BIG-O

definition
$f(n) = O(g(n))$ if there exist $c > 0$ and $n_0$ with $f(n) \le c\,g(n)$ for all $n \ge n_0$
meaning
an upper bound, up to a constant. Where the curves cross for small $n$ doesn't matter. Ω and Θ were named but not defined yet.
rule of thumb
keep the highest-order term and drop the constants: $2n^2+3n+5 \to O(n^2)$, $2n+c \to O(n)$, $3n\log n + 5n + 20 \to O(n\log n)$
ordernameexample from the course
O(1)constantmin/max of a sorted array (a₁, aₙ) · insert at the front of a linked list · push/pop · circular enqueue
O(log n)logarithmicbinary search
O(n)linearvector addition · max of an unsorted array · traversal · linear search
O(n log n)"poly-logarithmic" in lecture; linearithmic in most textbooksmany sorting algorithms (after the midterm)
O(n²)quadraticbubble sort · scanning every cell of an n × n matrix

The intuition (S06 primer, S11):

  • Two machines add vectors at 1 s and 2 s per step: $T_1 = c_1 n$ and $T_2 = c_2 n$. Different slopes, but both grow linearly, so they count as the same performance.
  • Doubling a sorted array doesn't change how you find its min or max: O(1). An unsorted max needs every element, because the biggest might be last: O(n).
  • A random-number generator takes zero inputs and still counts as an algorithm. An even/odd checker that only works up to 100 lacks generality.
  • Dr. Dey kept the formal definition for later: "those mathematical formulations will be taught by Professor Surya."
Find c and n₀

Slide c until the teal bound c·g(n) stays above the amber f(n) from some point on. That point is n₀. Dots are f at whole numbers of n: teal where the bound holds, red where it fails.

c

WORKED EXAMPLES

Prove $n^2 + 3n + 1 = O(n^2)$.S11
  1. We need $c, n_0$ with $n^2 + 3n + 1 \le c\,n^2$ for all $n \ge n_0$.
  2. Take $c = 5$, $n_0 = 1$. At $n = 1$: $5 \le 5$. At $n = 2$: $11 \le 20$.
  3. In general, for $n \ge 1$: $3n \le 3n^2$ and $1 \le n^2$, so the left side is at most $5n^2$.

c = 5, n₀ = 1

Count the steps: sum = 0; i = 1; input n; while i ≤ n: sum = sum + i; i = i + 1.S11
  1. Outside the loop: declare, sum = 0, i = 1 and input, which is 4 steps.
  2. Inside: 2 steps × n iterations = 2n.
  3. $T(n) = 2n + 4$. Exercise: $2n + 4 \le 10n \iff 4 \le 8n$, which is true for $n \ge 1$.

T(n) = 2n + 4 = O(n) · c = 10, n₀ = 1

PRACTICE

Find $c$ and $n_0$ for $3n + 5 = O(n)$ and for $5n^2 + 2n = O(n^2)$.practice
  1. $3n + 5 \le 8n \iff 5 \le 5n$, so $c = 8$, $n_0 = 1$. (Or $c = 4$, $n_0 = 5$: many pairs work.)
  2. $5n^2 + 2n \le 7n^2 \iff 2n \le 2n^2$, which is true for $n \ge 1$. So $c = 7$, $n_0 = 1$.

(8, 1) · (7, 1)

Is $n^2 = O(n)$?practice
  1. We'd need $n^2 \le c\,n$, i.e. $n \le c$, for all large $n$.
  2. Whatever $c$ you pick, $n = c + 1$ breaks it. No $n_0$ exists.

No. (n = O(n²) is true, though.)

every operation in the course, worst case

Complexity sheet

structureoperationtimenote
arrayaccess A[I] · 2-D / triangular / band indexO(1)one formula: a multiply plus additions
arraytraverse · linear searchO(n)search best case O(1)
arraybubble sortO(n²)n(n−1)/2 comparisons · O(1) extra space
arrayinsert into a sorted arrayO(n)shift everything after the slot
singly linkedinsert at front · delete firstO(1)
singly linkedinsert at end · delete last · after/delete key · search · traverseO(n)walk from the header
singly linkedcopy · concatenateO(n) · O(m)copy uses O(n) extra space; concatenate walks list 1 only
doubly linkedinsert at frontO(1)4 link updates
doubly linkedinsert at end: linear · circularO(n) · O(1)via HEADER->LLINK
sparse multi-linkedreach element (i, j)O(n)n + n steps, against O(1) for an array
stack (array or linked)push · pop · isEmpty · isFullO(1)linked status = count nodes, O(n)
stackevaluate postfix · reverse a stringO(n)each token or character once
linear array queueenqueue · dequeueO(1)but falsely "full"
linear array queuewith compaction · with shift-on-dequeueO(N)non-uniform: some operations are cheap, some expensive
circular / linked queueenqueue · dequeueO(1)pointer arithmetic only, no shifting