---
marp: true
theme: kurzus
paginate: true
footer: Data Structures · Week 11 · Dynamic stack and queue, hash table
---

<!-- _class: cim -->
<!-- _paginate: false -->
<!-- _footer: "" -->

# Data Structures

## Week 11 – Stack and queue based on lists; the hash table

Dr. Ferenc Koczka · Eszterházy Károly University · 2026

---

## Review

- **Stack** (week 7) and **queue** (week 8) with arrays: fixed size, a “full” state
- **Linked lists** (weeks 9–10): O(1) at the beginning, with an End pointer at the end too
- **Abstract data type:** the operations matter, not the implementation

### Questions for today

- How can a stack and a queue be implemented with a linked list?
- Can an element be found in **O(1)** time – without sorting and binary search?

<!--
Time plan: dynamic stack and queue 25' · the idea of the hash table, hash function 20' · collision handling 30' · built-in maps, summary 15'
-->

---

<!-- _class: fejezet -->

# Stack and queue with a linked list

---

## Stack with a linked list

![w:1000](kepek/h11_lista_verem.svg)

<div class="ket-hasab">
<div>

```text
Procedure Push(Top, Data)
  If Reserve(P) then
    Elem[P].Value ← Data
    Elem[P].Next ← Top
    Top ← P
  else ERROR
End procedure
```

</div>
<div>

```text
Function Pop(Top)
  If Top = Nil then ERROR
  x ← Elem[Top].Value
  P ← Top
  Top ← Elem[Top].Next
  Free(P)
  Return x
End function
```

</div>
</div>

<span class="halvany">This is exactly the “insertion at the beginning” and “deletion from the beginning” of week 9.</span>

---

<!-- _class: kicsi -->

## Queue with a linked list

![w:820](kepek/h11_lista_sor.svg)

<div class="ket-hasab">
<div>

```text
Procedure Enqueue(Front, Rear, Data)
  If Reserve(P) then
    Elem[P].Value ← Data
    Elem[P].Next ← Nil
    If Rear = Nil then Front ← P
    else Elem[Rear].Next ← P
    Rear ← P
  else ERROR
End procedure
```

</div>
<div>

```text
Function Dequeue(Front, Rear)
  If Front = Nil then ERROR
  x ← Elem[Front].Value
  P ← Front
  Front ← Elem[Front].Next
  If Front = Nil then Rear ← Nil
  Free(P)
  Return x
End function
```

</div>
</div>

<div class="doboz figyelem">

When the queue becomes empty, the **Rear** pointer must also be set to Nil!

</div>

---

<!-- _class: kicsi tomor -->

## One interface, two implementations

<div class="ket-hasab">
<div>

<span class="cimke java">Java</span>

```java
interface Stack<T> {
    void push(T x);
    T pop();
    boolean isEmpty();
}

class LinkedStack<T> implements Stack<T> {
    private Node<T> top;
    public void push(T x) { top = new Node<>(x, top); }
    public T pop() {
        T x = top.value;
        top = top.next;
        return x;
    }
    public boolean isEmpty() { return top == null; }
}

// the user knows only the interface:
String reverse(String s, Stack<Character> st)
```

</div>
<div>

<span class="cimke go">Go</span>

```go
type Stack[T any] interface {
    Push(x T)
    Pop() (T, error)
    IsEmpty() bool
}

type LinkedStack[T any] struct{ top *node[T] }

func (s *LinkedStack[T]) Push(x T) {
    s.top = &node[T]{x, s.top}
}

// any implementation can be passed:
reverse("数据结构", &LinkedStack[rune]{})
reverse("数据结构", &SliceStack[rune]{})
```

</div>
</div>

---

## Array or linked implementation?

| | With an array | With a linked list |
|---|---|---|
| Push / Pop, Enqueue / Dequeue | O(1) | O(1) |
| Size | fixed – a “full” state is possible | **unlimited** (while there is memory) |
| Space per element | only the value | value + **pointer** |
| Unused space | the empty part of the array | none |
| Cache-friendly | **yes** (contiguous) | less (scattered) |
| Allocation per element | none | every push/enqueue allocates |

<div class="doboz tipp">

In practice the **dynamic (circular) array** implementation is the most common (Java `ArrayDeque`, Go slice) – fast and memory-efficient.

</div>

---

<!-- _class: fejezet -->

# Hash table

---

## The problem: fast search

Our options so far for finding an element:

| Method | Search | Insertion |
|---|---|---|
| unsorted array / list | O(n) | O(1) |
| sorted array + binary search | O(log n) | O(n) |
| **boolean vector** (week 6) | **O(1)** | **O(1)** |

The boolean vector is fast, but it only works for a **small, known** universe.

<div class="doboz">

**Idea:** convert the key (an arbitrarily large number or a string) into a **small index** with a function, and store the element at that index of an array.

</div>

---

## Hash function

![w:880](kepek/h11_hash_fv.svg)

---

<!-- _class: kicsi -->

## A good hash function

**Hash function:** h: keys → {0, 1, …, m − 1}

Requirements:

- **Fast** to compute – O(length of the key)
- Spreads the keys **evenly** among the buckets
- **Deterministic:** always gives the same result for the same key

**Examples:**

- Integer key: **h(k) = k mod m** (m should be a prime)
- String: **polynomial hash**: h = (h · 31 + c) mod m for every character c
  Java: `"Li".hashCode()` = 'L' · 31 + 'i' = 76 · 31 + 105 = **2461**

<div class="doboz figyelem">

**Collision:** two different keys have the same hash value. It is unavoidable (there are more keys than buckets) – it **must be handled**!

</div>

---

## Collision handling 1: chaining

![w:900](kepek/h11_lancolas.svg)

---

<!-- _class: kicsi tomor -->

## Chaining – operations

<div class="ket-hasab">
<div>

<span class="cimke pszeudo">Pseudocode</span>

```text
Procedure Insert(T, key, value)
  i ← h(key)
  P ← Search(T[i], key)        // in the list
  If P ≠ Nil then
    Elem[P].Value ← value      // modification
  else
    InsertFirst(T[i], key, value)
  End if
End procedure

Function Find(T, key)
  Return SearchList(T[h(key)], key)
End function
```

</div>
<div>

<span class="cimke java">Java</span>

```java
void put(String key, int value) {
    int i = hash(key);
    for (Entry e = buckets[i]; e != null; e = e.next)
        if (e.key.equals(key)) { e.value = value; return; }
    buckets[i] = new Entry(key, value, buckets[i]);
    size++;
    if (size > 0.75 * buckets.length) resize();
}

Integer get(String key) {
    for (Entry e = buckets[hash(key)];
         e != null; e = e.next)
        if (e.key.equals(key)) return e.value;
    return null;
}
```

</div>
</div>

---

## Load factor and rehashing

**Load factor:** α = n / m (number of elements / number of buckets)

- With chaining the average list length is **α** → search is **O(1 + α)** on average
- If α is too large (e.g. > 0.75): **rehashing** – a table twice as large, and **every** element is placed again (the hash depends on *m*!)
- As with the dynamic array: one resize is O(n), but **amortized O(1)**

**Example** (`HashTable` program): m = 11, 8 elements (α = 0.73) – one collision (Li, Chen)
After the 9th element α = 0.82 > 0.75 → m = 22, α = 0.41 – no collision

---

## Collision handling 2: open addressing

![w:1000](kepek/h11_nyilt.svg)

- Every element is **in the array itself** – no lists
- On a collision we look for the **next** free slot (linear probing)
- **Deletion:** we cannot simply write “empty” – it would break the search chain → a “deleted” mark is needed

---

## Open addressing: the effect of the load factor

Average number of probes for a successful search (measured values, `LinearProbing` program):

| α | 0.25 | 0.50 | 0.75 | 0.90 | 0.99 |
|---|---|---|---|---|---|
| probes | 1.17 | 1.50 | 2.48 | 5.52 | **36.16** |

- With a low load factor we almost always find it at the **first** try
- As the table fills up, the occupied slots form **clusters** → drastic slowdown
- With open addressing α < 1 is mandatory; in practice α ≤ 0.5–0.75

---

## Efficiency of the hash table

| Operation | Average case | Worst case |
|---|---|---|
| Search | **O(1)** | O(n) |
| Insertion | **O(1)** (amortized) | O(n) |
| Deletion | **O(1)** | O(n) |

- Worst case: **every** key goes into the same bucket (a bad hash function, or malicious input)
- **No order:** minimum, maximum, sorted traversal are O(n) / O(n log n)
- Sorted keys need a **search tree** (week 15, Java `TreeMap`)

---

<!-- _class: kicsi -->

## Built-in hash tables

<div class="ket-hasab">
<div>

<span class="cimke java">Java – HashMap, HashSet</span>

```java
Map<String, Integer> freq = new HashMap<>();
for (String w : text.split(" "))
    freq.merge(w, 1, Integer::sum); // freq[w]++

freq.get("fox");                // 2
freq.getOrDefault("cat", 0);    // 0
freq.containsKey("dog");        // true
freq.remove("the");

Set<String> seen = new HashSet<>();
```

The `hashCode()` and `equals()` methods of the key class must be **consistent**!

</div>
<div>

<span class="cimke go">Go – map</span>

```go
freq := map[string]int{}
for _, w := range strings.Fields(text) {
    freq[w]++          // missing key: 0
}

v, ok := freq["cat"]   // "comma ok"
delete(freq, "the")
len(freq)

seen := map[string]bool{}
```

The iteration order of a map is **random** – on purpose!

</div>
</div>

**Applications:** dictionaries, frequencies, caches, database indexes, filtering duplicates, symbol tables of compilers

---

## Summary

- **Stack with a list:** push/pop at the beginning of the list · **queue with a list:** Front + Rear pointers – all O(1), no “full” state
- The same **interface** with several implementations
- **Hash table:** an index from the key with a hash function → search, insertion, deletion **O(1)** on average
- **Collision handling:** chaining (a list per bucket) or open addressing (next free slot)
- **Load factor** α = n/m; too large α → rehashing
- Java `HashMap`/`HashSet` · Go `map`

**Next week:** recursion.

---

## Review questions

1. Why does the linked stack work at the **beginning** of the list and not at the end?
2. What happens in the linked queue if we remove the last element and forget to set the Rear pointer to Nil?
3. h(k) = k mod 7, with chaining. Insert: 10, 3, 17, 24, 5! Draw the table!
4. The same keys with linear probing, m = 7 – where does 24 go?
5. True or false: the search time of a hash table is O(1) even in the worst case.
6. Why can the smallest key not be found efficiently in a hash table?

<!--
Answers:
1. At the beginning both push and pop are O(1); at the end deletion would require finding the last but one (O(n)).
2. Rear points to an already freed element; the next Enqueue would write its Next field, while Front stays Nil → the new element is lost.
3. 10→3, 3→3, 17→3, 24→3, 5→5: T[3]: 24 → 17 → 3 → 10 (inserted at the front), T[5]: 5
4. 10→3, 3→4, 17→5, 24→6, 5→5 occupied, 6 occupied → 0. 24 goes to slot 6.
5. F – O(1) on average, O(n) in the worst case.
6. The hash function scatters the keys, there is no order – every element must be checked (O(n)).
-->

---

<!-- _class: fejezet -->
<!-- _footer: "" -->

# Practice

---

<!-- _class: gyak -->

## Demo programs

| Program | Java | Go | What does it show? |
|---|---|---|---|
| Linked stack and queue | `LinkedStackQueue.java` | `w11/linkedstackqueue` | interface, two implementations |
| Hash table | `HashTable.java` | `w11/hashtable` | chaining, collision, rehashing |
| Linear probing | `LinearProbing.java` | `w11/linearprobing` | open addressing, effect of the load factor |
| Word frequency | `WordCount.java` | `w11/wordcount` | using `HashMap` / `map` |

---

<!-- _class: gyak -->

## Exercises

1. **Two sum:** are there two elements in the array whose sum is a given value? O(n) with a hash table instead of O(n²)!
2. **First unique character:** which is the first character that occurs only once? (`"swiss" → 'w'`)
3. **Anagram groups:** group the words so that anagrams of each other are in the same group!
4. **Duplicates:** is there a repeated element in the array? (O(n) on average)
5. **Brackets:** the task of week 7, with the bracket pairs stored in a map.
6. **Phone book:** name → phone number, search by name.

Solutions: `Exercises11.java`, `w11/exercises`

---

<!-- _class: gyak -->

## Homework

1. Extend our own `HashTable` class with a `keys()` method that returns every key! How many steps does it take?
2. Implement **deletion** in the linear probing table with a “deleted” mark! How do search and insertion change?
3. Measure the number of collisions of your own hash table for 10 000 random words with two hash functions: (a) the sum of the character codes, (b) polynomial hash! What do you observe?
4. Implement an **LRU cache** with a hash table and a doubly linked list (see the homework of week 10), where both `get` and `put` are O(1)!

<!--
Solution outline:
1. We walk through every list of every bucket: O(m + n).
2. DELETED mark: search continues past it (does not stop), insertion may reuse it as a free slot.
3. The sum hash puts anagrams (and many other words) into the same bucket – far more collisions.
4. HashMap<key, list node> + doubly linked list; get: unlink the node and move it to the front; put: when full, delete from the end and from the map too.
-->
