Ref: 2 Rename folder to follow previous articles
This commit is contained in:
BIN
analysis/04-Reverse_Linked_List/diagrams/00_initial_state.png
Normal file
BIN
analysis/04-Reverse_Linked_List/diagrams/00_initial_state.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 23 KiB |
@@ -0,0 +1,35 @@
|
||||
@startuml
|
||||
title Step 0 — Initial State
|
||||
|
||||
object "Node 1" as n1 {
|
||||
value = 1
|
||||
}
|
||||
|
||||
object "Node 2" as n2 {
|
||||
value = 2
|
||||
}
|
||||
|
||||
object "Node 3" as n3 {
|
||||
value = 3
|
||||
}
|
||||
|
||||
n1 --> n2 : next
|
||||
n2 --> n3 : next
|
||||
n3 --> "null" : next
|
||||
|
||||
object "previous" as prev
|
||||
object "current" as cur
|
||||
|
||||
prev --> "null"
|
||||
cur --> n1
|
||||
|
||||
note bottom
|
||||
Before the loop starts:
|
||||
|
||||
previous = null
|
||||
current = head
|
||||
|
||||
The original list is still intact.
|
||||
end note
|
||||
|
||||
@enduml
|
||||
BIN
analysis/04-Reverse_Linked_List/diagrams/01_save_next.png
Normal file
BIN
analysis/04-Reverse_Linked_List/diagrams/01_save_next.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 28 KiB |
38
analysis/04-Reverse_Linked_List/diagrams/01_save_next.puml
Normal file
38
analysis/04-Reverse_Linked_List/diagrams/01_save_next.puml
Normal file
@@ -0,0 +1,38 @@
|
||||
@startuml
|
||||
title Step 1 — Save next
|
||||
|
||||
object "Node 1" as n1 {
|
||||
value = 1
|
||||
}
|
||||
|
||||
object "Node 2" as n2 {
|
||||
value = 2
|
||||
}
|
||||
|
||||
object "Node 3" as n3 {
|
||||
value = 3
|
||||
}
|
||||
|
||||
n1 --> n2 : next
|
||||
n2 --> n3 : next
|
||||
n3 --> "null" : next
|
||||
|
||||
object "previous" as prev
|
||||
object "current" as cur
|
||||
object "next" as nxt
|
||||
|
||||
prev --> "null"
|
||||
cur --> n1
|
||||
nxt --> n2
|
||||
|
||||
note right of nxt
|
||||
next = current->next
|
||||
|
||||
We save the next node before changing
|
||||
current->next.
|
||||
|
||||
Without this temporary pointer, the rest
|
||||
of the original list would become unreachable.
|
||||
end note
|
||||
|
||||
@enduml
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 25 KiB |
@@ -0,0 +1,38 @@
|
||||
@startuml
|
||||
title Step 2 — Reverse current->next
|
||||
|
||||
object "Node 1" as n1 {
|
||||
value = 1
|
||||
}
|
||||
|
||||
object "Node 2" as n2 {
|
||||
value = 2
|
||||
}
|
||||
|
||||
object "Node 3" as n3 {
|
||||
value = 3
|
||||
}
|
||||
|
||||
n1 --> "null" : next
|
||||
n2 --> n3 : next
|
||||
n3 --> "null" : next
|
||||
|
||||
object "previous" as prev
|
||||
object "current" as cur
|
||||
object "next" as nxt
|
||||
|
||||
prev --> "null"
|
||||
cur --> n1
|
||||
nxt --> n2
|
||||
|
||||
note right of n1
|
||||
current->next = previous
|
||||
|
||||
Node 1 no longer points to Node 2.
|
||||
It now points to the already reversed part.
|
||||
|
||||
At the first iteration, the reversed part is empty,
|
||||
so Node 1 points to null.
|
||||
end note
|
||||
|
||||
@enduml
|
||||
BIN
analysis/04-Reverse_Linked_List/diagrams/03_move_pointers.png
Normal file
BIN
analysis/04-Reverse_Linked_List/diagrams/03_move_pointers.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 23 KiB |
@@ -0,0 +1,39 @@
|
||||
@startuml
|
||||
title Step 3 — Move previous and current
|
||||
|
||||
object "Node 1" as n1 {
|
||||
value = 1
|
||||
}
|
||||
|
||||
object "Node 2" as n2 {
|
||||
value = 2
|
||||
}
|
||||
|
||||
object "Node 3" as n3 {
|
||||
value = 3
|
||||
}
|
||||
|
||||
n1 --> "null" : next
|
||||
n2 --> n3 : next
|
||||
n3 --> "null" : next
|
||||
|
||||
object "previous" as prev
|
||||
object "current" as cur
|
||||
|
||||
prev --> n1
|
||||
cur --> n2
|
||||
|
||||
note right
|
||||
previous = current
|
||||
current = next
|
||||
|
||||
The reversed part is now:
|
||||
|
||||
1 -> null
|
||||
|
||||
The remaining original part is still:
|
||||
|
||||
2 -> 3 -> null
|
||||
end note
|
||||
|
||||
@enduml
|
||||
BIN
analysis/04-Reverse_Linked_List/diagrams/04_second_iteration.png
Normal file
BIN
analysis/04-Reverse_Linked_List/diagrams/04_second_iteration.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 25 KiB |
@@ -0,0 +1,37 @@
|
||||
@startuml
|
||||
title Step 4 — Second Iteration After Reversing Node 2
|
||||
|
||||
object "Node 1" as n1 {
|
||||
value = 1
|
||||
}
|
||||
|
||||
object "Node 2" as n2 {
|
||||
value = 2
|
||||
}
|
||||
|
||||
object "Node 3" as n3 {
|
||||
value = 3
|
||||
}
|
||||
|
||||
n2 --> n1 : next
|
||||
n1 --> "null" : next
|
||||
n3 --> "null" : next
|
||||
|
||||
object "previous" as prev
|
||||
object "current" as cur
|
||||
object "next" as nxt
|
||||
|
||||
prev --> n2
|
||||
cur --> n3
|
||||
nxt --> n3
|
||||
|
||||
note bottom
|
||||
After processing Node 2:
|
||||
|
||||
2 -> 1 -> null
|
||||
|
||||
The reversed part grows from the front.
|
||||
The remaining part starts at current.
|
||||
end note
|
||||
|
||||
@enduml
|
||||
BIN
analysis/04-Reverse_Linked_List/diagrams/05_final_state.png
Normal file
BIN
analysis/04-Reverse_Linked_List/diagrams/05_final_state.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 23 KiB |
35
analysis/04-Reverse_Linked_List/diagrams/05_final_state.puml
Normal file
35
analysis/04-Reverse_Linked_List/diagrams/05_final_state.puml
Normal file
@@ -0,0 +1,35 @@
|
||||
@startuml
|
||||
title Step 5 — Final State
|
||||
|
||||
object "Node 1" as n1 {
|
||||
value = 1
|
||||
}
|
||||
|
||||
object "Node 2" as n2 {
|
||||
value = 2
|
||||
}
|
||||
|
||||
object "Node 3" as n3 {
|
||||
value = 3
|
||||
}
|
||||
|
||||
n3 --> n2 : next
|
||||
n2 --> n1 : next
|
||||
n1 --> "null" : next
|
||||
|
||||
object "head" as head
|
||||
object "previous" as prev
|
||||
object "current" as cur
|
||||
|
||||
head --> n3
|
||||
prev --> n3
|
||||
cur --> "null"
|
||||
|
||||
note right of head
|
||||
When current becomes null,
|
||||
previous points to the new head.
|
||||
|
||||
head = previous
|
||||
end note
|
||||
|
||||
@enduml
|
||||
58
analysis/04-Reverse_Linked_List/diagrams/README.md
Normal file
58
analysis/04-Reverse_Linked_List/diagrams/README.md
Normal file
@@ -0,0 +1,58 @@
|
||||
# Reverse Linked List — PlantUML Diagrams
|
||||
|
||||
This directory contains PlantUML diagrams for the three-pointer linked list reversal algorithm.
|
||||
|
||||
The diagrams use a small list:
|
||||
|
||||
```text
|
||||
1 -> 2 -> 3 -> null
|
||||
```
|
||||
|
||||
and show how it becomes:
|
||||
|
||||
```text
|
||||
3 -> 2 -> 1 -> null
|
||||
```
|
||||
|
||||
## Files
|
||||
|
||||
- `00_initial_state.puml` — initial state before the loop
|
||||
- `01_save_next.puml` — saving `next = current->next`
|
||||
- `02_reverse_current_link.puml` — reversing `current->next`
|
||||
- `03_move_pointers.puml` — moving `previous` and `current`
|
||||
- `04_second_iteration.puml` — state after the second node is processed
|
||||
- `05_final_state.puml` — final state after the loop
|
||||
|
||||
## Generate PNG Files
|
||||
|
||||
```sh
|
||||
plantuml diagrams/*.puml
|
||||
```
|
||||
|
||||
## Generate SVG Files
|
||||
|
||||
```sh
|
||||
plantuml -tsvg diagrams/*.puml
|
||||
```
|
||||
|
||||
## Core Idea
|
||||
|
||||
During the loop, the list is logically split into two parts:
|
||||
|
||||
- `previous` points to the already reversed part
|
||||
- `current` points to the node currently being processed
|
||||
- `next` temporarily preserves access to the remaining original list
|
||||
|
||||
The key operation is:
|
||||
|
||||
```cpp
|
||||
current->next = previous;
|
||||
```
|
||||
|
||||
But this is only safe after saving:
|
||||
|
||||
```cpp
|
||||
Node* next = current->next;
|
||||
```
|
||||
|
||||
Otherwise the remaining part of the original list would be lost.
|
||||
74
analysis/04-Reverse_Linked_List/exapmle/reverse-linked-list/.gitignore
vendored
Normal file
74
analysis/04-Reverse_Linked_List/exapmle/reverse-linked-list/.gitignore
vendored
Normal file
@@ -0,0 +1,74 @@
|
||||
# This file is used to ignore files which are generated
|
||||
# ----------------------------------------------------------------------------
|
||||
|
||||
*~
|
||||
*.autosave
|
||||
*.a
|
||||
*.core
|
||||
*.moc
|
||||
*.o
|
||||
*.obj
|
||||
*.orig
|
||||
*.rej
|
||||
*.so
|
||||
*.so.*
|
||||
*_pch.h.cpp
|
||||
*_resource.rc
|
||||
*.qm
|
||||
.#*
|
||||
*.*#
|
||||
core
|
||||
!core/
|
||||
tags
|
||||
.DS_Store
|
||||
.directory
|
||||
*.debug
|
||||
Makefile*
|
||||
*.prl
|
||||
*.app
|
||||
moc_*.cpp
|
||||
ui_*.h
|
||||
qrc_*.cpp
|
||||
Thumbs.db
|
||||
*.res
|
||||
*.rc
|
||||
/.qmake.cache
|
||||
/.qmake.stash
|
||||
|
||||
# qtcreator generated files
|
||||
*.pro.user*
|
||||
CMakeLists.txt.user*
|
||||
|
||||
# xemacs temporary files
|
||||
*.flc
|
||||
|
||||
# Vim temporary files
|
||||
.*.swp
|
||||
|
||||
# Visual Studio generated files
|
||||
*.ib_pdb_index
|
||||
*.idb
|
||||
*.ilk
|
||||
*.pdb
|
||||
*.sln
|
||||
*.suo
|
||||
*.vcproj
|
||||
*vcproj.*.*.user
|
||||
*.ncb
|
||||
*.sdf
|
||||
*.opensdf
|
||||
*.vcxproj
|
||||
*vcxproj.*
|
||||
|
||||
# MinGW generated files
|
||||
*.Debug
|
||||
*.Release
|
||||
|
||||
# Python byte code
|
||||
*.pyc
|
||||
|
||||
# Binaries
|
||||
# --------
|
||||
*.dll
|
||||
*.exe
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
# Reverse Linked List Example
|
||||
|
||||
This directory contains a small standalone C++ example for the classic three-pointer linked list reversal algorithm.
|
||||
|
||||
## Build
|
||||
|
||||
```bash
|
||||
g++ -std=c++17 -Wall -Wextra -pedantic main.cpp -o reverse_linked_list
|
||||
```
|
||||
|
||||
## Run
|
||||
|
||||
```bash
|
||||
./reverse_linked_list
|
||||
```
|
||||
|
||||
## Expected Output
|
||||
|
||||
```text
|
||||
Original list:
|
||||
1 -> 2 -> 3 -> 4 -> 5 -> null
|
||||
|
||||
Reversed list:
|
||||
5 -> 4 -> 3 -> 2 -> 1 -> null
|
||||
```
|
||||
|
||||
|
||||
## Memory walkthrough
|
||||
|
||||
see memory_walkthrough.md
|
||||
@@ -0,0 +1,216 @@
|
||||
/**
|
||||
* @file main.cpp
|
||||
* @brief Demonstrates the classic three-pointer algorithm for reversing a singly linked list.
|
||||
*
|
||||
* This example is intentionally small and self-contained.
|
||||
* It is not meant to show that reversing linked lists is a common production task.
|
||||
* Instead, it documents the interview pattern clearly enough that a reader unfamiliar
|
||||
* with it can compile the program, run it, and inspect the output.
|
||||
*/
|
||||
|
||||
#include <iostream>
|
||||
#include <initializer_list>
|
||||
|
||||
/**
|
||||
* @brief A minimal singly linked list node.
|
||||
*
|
||||
* Each node stores an integer value and a pointer to the next node.
|
||||
* The last node in the list has @c next equal to @c nullptr.
|
||||
*/
|
||||
struct Node {
|
||||
int value; ///< Payload stored in the node.
|
||||
Node *next; ///< Pointer to the next node, or nullptr for the last node.
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Appends a new value to the end of the list.
|
||||
*
|
||||
* @param head Reference to the head pointer of the list.
|
||||
* @param value Value to store in the new node.
|
||||
*
|
||||
* This helper is used only to build the demonstration list.
|
||||
* It keeps the example simple and avoids using STL containers for the list itself,
|
||||
* because the goal is to demonstrate raw pointer manipulation.
|
||||
*/
|
||||
void appendNode (Node *&head, int value) {
|
||||
Node *node = new Node{value, nullptr};
|
||||
|
||||
if (head == nullptr) {
|
||||
head = node;
|
||||
return;
|
||||
}
|
||||
|
||||
Node *current = head;
|
||||
|
||||
while (current->next != nullptr)
|
||||
current = current->next;
|
||||
|
||||
current->next = node;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Creates a linked list from an initializer list.
|
||||
*
|
||||
* @param values Values to insert into the list in the given order.
|
||||
* @return Pointer to the first node of the created list.
|
||||
*
|
||||
* The caller owns the returned list and must release it with freeList().
|
||||
*/
|
||||
Node *createList (std::initializer_list<int> values) {
|
||||
Node *head = nullptr;
|
||||
|
||||
for (int value : values)
|
||||
appendNode (head, value);
|
||||
|
||||
return head;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Prints the list without modifying it.
|
||||
*
|
||||
* @param head Pointer to the first node of the list.
|
||||
*
|
||||
* Output example:
|
||||
* @code
|
||||
* 1 -> 2 -> 3 -> 4 -> null
|
||||
* @endcode
|
||||
*/
|
||||
void printList (const Node *head) {
|
||||
const Node *current = head;
|
||||
|
||||
while (current != nullptr) {
|
||||
std::cout << current->value << " -> ";
|
||||
current = current->next;
|
||||
}
|
||||
|
||||
std::cout << "null" << std::endl;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Reverses a singly linked list in place.
|
||||
*
|
||||
* @param head Pointer to the first node of the original list.
|
||||
* @return Pointer to the first node of the reversed list.
|
||||
*
|
||||
* This is the classic three-pointer interview algorithm.
|
||||
*
|
||||
* The three pointers are:
|
||||
*
|
||||
* - @c previous — the already reversed part of the list
|
||||
* - @c current — the node we are processing right now
|
||||
* - @c next — the original next node saved before we overwrite @c current->next
|
||||
*
|
||||
* Why @c next is necessary:
|
||||
*
|
||||
* In a singly linked list, each node only knows where the next node is.
|
||||
* When we execute:
|
||||
*
|
||||
* @code
|
||||
* current->next = previous;
|
||||
* @endcode
|
||||
*
|
||||
* we destroy the original forward link.
|
||||
* Without saving it first, the rest of the list would be lost.
|
||||
*
|
||||
* The algorithm works by moving one node at a time from the original forward chain
|
||||
* into the reversed chain.
|
||||
*
|
||||
* Initial state:
|
||||
*
|
||||
* @code
|
||||
* previous = null
|
||||
* current = 1 -> 2 -> 3 -> 4 -> null
|
||||
* @endcode
|
||||
*
|
||||
* After processing node 1:
|
||||
*
|
||||
* @code
|
||||
* previous = 1 -> null
|
||||
* current = 2 -> 3 -> 4 -> null
|
||||
* @endcode
|
||||
*
|
||||
* After processing node 2:
|
||||
*
|
||||
* @code
|
||||
* previous = 2 -> 1 -> null
|
||||
* current = 3 -> 4 -> null
|
||||
* @endcode
|
||||
*
|
||||
* When @c current becomes @c nullptr, @c previous points to the new head.
|
||||
*
|
||||
* Complexity:
|
||||
*
|
||||
* - Time: O(n), because each node is visited once.
|
||||
* - Extra memory: O(1), because only a fixed number of pointers is used.
|
||||
*/
|
||||
Node *reverseList (Node *head) {
|
||||
Node *previous = nullptr;
|
||||
Node *current = head;
|
||||
|
||||
while (current != nullptr) {
|
||||
/*
|
||||
* Save the original next node before changing current->next.
|
||||
* Without this line, the rest of the list would become unreachable.
|
||||
*/
|
||||
Node *next = current->next;
|
||||
|
||||
/*
|
||||
* Reverse the direction of the link.
|
||||
* The current node now points to the already reversed part.
|
||||
*/
|
||||
current->next = previous;
|
||||
|
||||
/*
|
||||
* Move previous forward.
|
||||
* The current node becomes the new head of the reversed part.
|
||||
*/
|
||||
previous = current;
|
||||
|
||||
/*
|
||||
* Continue with the node that originally followed current.
|
||||
*/
|
||||
current = next;
|
||||
}
|
||||
|
||||
return previous;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Releases all nodes in the list.
|
||||
*
|
||||
* @param head Pointer to the first node of the list.
|
||||
*
|
||||
* This function is separated from the reversal and printing logic.
|
||||
* It exists only because this example uses raw @c new to keep the node structure explicit.
|
||||
*/
|
||||
void freeList (Node *head) {
|
||||
Node *current = head;
|
||||
|
||||
while (current != nullptr) {
|
||||
Node *next = current->next;
|
||||
delete current;
|
||||
current = next;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Program entry point.
|
||||
*
|
||||
* Builds a small list, prints it, reverses it, prints it again,
|
||||
* and finally releases all allocated nodes.
|
||||
*/
|
||||
int main() {
|
||||
Node *list = createList ({1, 2, 3, 4, 5});
|
||||
|
||||
std::cout << "Original list:" << std::endl;
|
||||
printList (list);
|
||||
|
||||
list = reverseList (list);
|
||||
|
||||
std::cout << "\nReversed list:" << std::endl;
|
||||
printList (list);
|
||||
|
||||
freeList (list);
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,827 @@
|
||||
# Reverse Linked List — Memory Walkthrough
|
||||
|
||||
This walkthrough explains the classic three-pointer linked list reversal using a memory-oriented view.
|
||||
|
||||
The goal is not only to show that the algorithm works, but also to show what happens to:
|
||||
|
||||
- stack variables
|
||||
- heap nodes
|
||||
- `next` fields inside each node
|
||||
|
||||
The example list contains three nodes:
|
||||
|
||||
```text
|
||||
1 -> 2 -> 3 -> null
|
||||
```
|
||||
|
||||
For clarity, fake addresses are used:
|
||||
|
||||
```text
|
||||
Node 1: 0x1000
|
||||
Node 2: 0x2000
|
||||
Node 3: 0x3000
|
||||
```
|
||||
|
||||
These addresses are illustrative only.
|
||||
A real program will use different addresses.
|
||||
|
||||
---
|
||||
|
||||
## Algorithm
|
||||
|
||||
```cpp
|
||||
Node* reverseList(Node* head) {
|
||||
Node* previous = nullptr;
|
||||
Node* current = head;
|
||||
|
||||
while(current != nullptr) {
|
||||
Node* next = current->next;
|
||||
|
||||
current->next = previous;
|
||||
|
||||
previous = current;
|
||||
current = next;
|
||||
}
|
||||
|
||||
return previous;
|
||||
}
|
||||
```
|
||||
|
||||
The three important pointers are:
|
||||
|
||||
| Pointer | Meaning |
|
||||
|---|---|
|
||||
| `previous` | Head of the already reversed part |
|
||||
| `current` | Node currently being processed |
|
||||
| `next` | Saved pointer to the remaining original list |
|
||||
|
||||
The most important rule is:
|
||||
|
||||
> Save `next` before changing `current->next`.
|
||||
|
||||
Otherwise the rest of the original list may become unreachable.
|
||||
|
||||
---
|
||||
|
||||
## Step 0 — Initial State
|
||||
|
||||
Before the loop starts:
|
||||
|
||||
```cpp
|
||||
Node* previous = nullptr;
|
||||
Node* current = head;
|
||||
```
|
||||
|
||||
### Stack
|
||||
|
||||
```text
|
||||
+----------+----------+
|
||||
| Variable | Value |
|
||||
+----------+----------+
|
||||
| head | 0x1000 |
|
||||
| previous | nullptr |
|
||||
| current | 0x1000 |
|
||||
| next | not set |
|
||||
+----------+----------+
|
||||
```
|
||||
|
||||
### Heap
|
||||
|
||||
```text
|
||||
0x1000
|
||||
+-----------+-----------+
|
||||
| value = 1 | next=2000 |
|
||||
+-----------+-----------+
|
||||
|
||||
0x2000
|
||||
+-----------+-----------+
|
||||
| value = 2 | next=3000 |
|
||||
+-----------+-----------+
|
||||
|
||||
0x3000
|
||||
+-----------+-----------+
|
||||
| value = 3 | next=null |
|
||||
+-----------+-----------+
|
||||
```
|
||||
|
||||
### Logical view
|
||||
|
||||
```text
|
||||
head/current
|
||||
|
|
||||
v
|
||||
1 -> 2 -> 3 -> null
|
||||
|
||||
previous -> null
|
||||
```
|
||||
|
||||
At this point, nothing has been reversed yet.
|
||||
|
||||
---
|
||||
|
||||
## Step 1 — Save `next`
|
||||
|
||||
Inside the first loop iteration:
|
||||
|
||||
```cpp
|
||||
Node* next = current->next;
|
||||
```
|
||||
|
||||
`current` points to Node 1.
|
||||
`current->next` points to Node 2.
|
||||
|
||||
So:
|
||||
|
||||
```text
|
||||
next = 0x2000
|
||||
```
|
||||
|
||||
### Stack
|
||||
|
||||
```text
|
||||
+----------+----------+
|
||||
| Variable | Value |
|
||||
+----------+----------+
|
||||
| head | 0x1000 |
|
||||
| previous | nullptr |
|
||||
| current | 0x1000 |
|
||||
| next | 0x2000 |
|
||||
+----------+----------+
|
||||
```
|
||||
|
||||
### Heap
|
||||
|
||||
```text
|
||||
0x1000
|
||||
+-----------+-----------+
|
||||
| value = 1 | next=2000 |
|
||||
+-----------+-----------+
|
||||
|
||||
0x2000
|
||||
+-----------+-----------+
|
||||
| value = 2 | next=3000 |
|
||||
+-----------+-----------+
|
||||
|
||||
0x3000
|
||||
+-----------+-----------+
|
||||
| value = 3 | next=null |
|
||||
+-----------+-----------+
|
||||
```
|
||||
|
||||
Nothing in the heap changed yet.
|
||||
|
||||
The `next` stack variable only saves access to the rest of the list.
|
||||
|
||||
Without this temporary pointer, Node 2 and Node 3 could be lost after the next operation.
|
||||
|
||||
---
|
||||
|
||||
## Step 2 — Reverse `current->next`
|
||||
|
||||
Now the algorithm changes the link:
|
||||
|
||||
```cpp
|
||||
current->next = previous;
|
||||
```
|
||||
|
||||
`current` is Node 1.
|
||||
`previous` is `nullptr`.
|
||||
|
||||
So Node 1 no longer points to Node 2.
|
||||
It now points to `nullptr`.
|
||||
|
||||
### Stack
|
||||
|
||||
```text
|
||||
+----------+----------+
|
||||
| Variable | Value |
|
||||
+----------+----------+
|
||||
| head | 0x1000 |
|
||||
| previous | nullptr |
|
||||
| current | 0x1000 |
|
||||
| next | 0x2000 |
|
||||
+----------+----------+
|
||||
```
|
||||
|
||||
### Heap
|
||||
|
||||
```text
|
||||
0x1000
|
||||
+-----------+-----------+
|
||||
| value = 1 | next=null |
|
||||
+-----------+-----------+
|
||||
|
||||
0x2000
|
||||
+-----------+-----------+
|
||||
| value = 2 | next=3000 |
|
||||
+-----------+-----------+
|
||||
|
||||
0x3000
|
||||
+-----------+-----------+
|
||||
| value = 3 | next=null |
|
||||
+-----------+-----------+
|
||||
```
|
||||
|
||||
### Logical view
|
||||
|
||||
```text
|
||||
current
|
||||
|
|
||||
v
|
||||
1 -> null
|
||||
|
||||
next
|
||||
|
|
||||
v
|
||||
2 -> 3 -> null
|
||||
```
|
||||
|
||||
This is the key mutation.
|
||||
|
||||
The original list is now split into two logical parts:
|
||||
|
||||
```text
|
||||
Reversed part:
|
||||
1 -> null
|
||||
|
||||
Remaining original part:
|
||||
2 -> 3 -> null
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Step 3 — Move `previous`
|
||||
|
||||
The algorithm advances the reversed part:
|
||||
|
||||
```cpp
|
||||
previous = current;
|
||||
```
|
||||
|
||||
`previous` now points to Node 1.
|
||||
|
||||
### Stack
|
||||
|
||||
```text
|
||||
+----------+----------+
|
||||
| Variable | Value |
|
||||
+----------+----------+
|
||||
| head | 0x1000 |
|
||||
| previous | 0x1000 |
|
||||
| current | 0x1000 |
|
||||
| next | 0x2000 |
|
||||
+----------+----------+
|
||||
```
|
||||
|
||||
### Heap
|
||||
|
||||
```text
|
||||
0x1000
|
||||
+-----------+-----------+
|
||||
| value = 1 | next=null |
|
||||
+-----------+-----------+
|
||||
|
||||
0x2000
|
||||
+-----------+-----------+
|
||||
| value = 2 | next=3000 |
|
||||
+-----------+-----------+
|
||||
|
||||
0x3000
|
||||
+-----------+-----------+
|
||||
| value = 3 | next=null |
|
||||
+-----------+-----------+
|
||||
```
|
||||
|
||||
### Logical view
|
||||
|
||||
```text
|
||||
previous/current
|
||||
|
|
||||
v
|
||||
1 -> null
|
||||
|
||||
next
|
||||
|
|
||||
v
|
||||
2 -> 3 -> null
|
||||
```
|
||||
|
||||
The reversed part now officially starts at `previous`.
|
||||
|
||||
---
|
||||
|
||||
## Step 4 — Move `current`
|
||||
|
||||
The algorithm continues with the saved next node:
|
||||
|
||||
```cpp
|
||||
current = next;
|
||||
```
|
||||
|
||||
`current` now points to Node 2.
|
||||
|
||||
### Stack
|
||||
|
||||
```text
|
||||
+----------+----------+
|
||||
| Variable | Value |
|
||||
+----------+----------+
|
||||
| head | 0x1000 |
|
||||
| previous | 0x1000 |
|
||||
| current | 0x2000 |
|
||||
| next | 0x2000 |
|
||||
+----------+----------+
|
||||
```
|
||||
|
||||
### Heap
|
||||
|
||||
```text
|
||||
0x1000
|
||||
+-----------+-----------+
|
||||
| value = 1 | next=null |
|
||||
+-----------+-----------+
|
||||
|
||||
0x2000
|
||||
+-----------+-----------+
|
||||
| value = 2 | next=3000 |
|
||||
+-----------+-----------+
|
||||
|
||||
0x3000
|
||||
+-----------+-----------+
|
||||
| value = 3 | next=null |
|
||||
+-----------+-----------+
|
||||
```
|
||||
|
||||
### Logical view
|
||||
|
||||
```text
|
||||
previous
|
||||
|
|
||||
v
|
||||
1 -> null
|
||||
|
||||
current
|
||||
|
|
||||
v
|
||||
2 -> 3 -> null
|
||||
```
|
||||
|
||||
The first iteration is complete.
|
||||
|
||||
---
|
||||
|
||||
## Step 5 — Second Iteration: Save `next`
|
||||
|
||||
The loop repeats.
|
||||
|
||||
```cpp
|
||||
Node* next = current->next;
|
||||
```
|
||||
|
||||
`current` points to Node 2.
|
||||
Node 2 points to Node 3.
|
||||
|
||||
So:
|
||||
|
||||
```text
|
||||
next = 0x3000
|
||||
```
|
||||
|
||||
### Stack
|
||||
|
||||
```text
|
||||
+----------+----------+
|
||||
| Variable | Value |
|
||||
+----------+----------+
|
||||
| head | 0x1000 |
|
||||
| previous | 0x1000 |
|
||||
| current | 0x2000 |
|
||||
| next | 0x3000 |
|
||||
+----------+----------+
|
||||
```
|
||||
|
||||
### Heap
|
||||
|
||||
```text
|
||||
0x1000
|
||||
+-----------+-----------+
|
||||
| value = 1 | next=null |
|
||||
+-----------+-----------+
|
||||
|
||||
0x2000
|
||||
+-----------+-----------+
|
||||
| value = 2 | next=3000 |
|
||||
+-----------+-----------+
|
||||
|
||||
0x3000
|
||||
+-----------+-----------+
|
||||
| value = 3 | next=null |
|
||||
+-----------+-----------+
|
||||
```
|
||||
|
||||
Again, the heap has not changed yet.
|
||||
|
||||
---
|
||||
|
||||
## Step 6 — Second Iteration: Reverse Link
|
||||
|
||||
```cpp
|
||||
current->next = previous;
|
||||
```
|
||||
|
||||
`current` is Node 2.
|
||||
`previous` is Node 1.
|
||||
|
||||
So Node 2 now points back to Node 1.
|
||||
|
||||
### Stack
|
||||
|
||||
```text
|
||||
+----------+----------+
|
||||
| Variable | Value |
|
||||
+----------+----------+
|
||||
| head | 0x1000 |
|
||||
| previous | 0x1000 |
|
||||
| current | 0x2000 |
|
||||
| next | 0x3000 |
|
||||
+----------+----------+
|
||||
```
|
||||
|
||||
### Heap
|
||||
|
||||
```text
|
||||
0x1000
|
||||
+-----------+-----------+
|
||||
| value = 1 | next=null |
|
||||
+-----------+-----------+
|
||||
|
||||
0x2000
|
||||
+-----------+-----------+
|
||||
| value = 2 | next=1000 |
|
||||
+-----------+-----------+
|
||||
|
||||
0x3000
|
||||
+-----------+-----------+
|
||||
| value = 3 | next=null |
|
||||
+-----------+-----------+
|
||||
```
|
||||
|
||||
### Logical view
|
||||
|
||||
```text
|
||||
current
|
||||
|
|
||||
v
|
||||
2 -> 1 -> null
|
||||
|
||||
next
|
||||
|
|
||||
v
|
||||
3 -> null
|
||||
```
|
||||
|
||||
The reversed part will become:
|
||||
|
||||
```text
|
||||
2 -> 1 -> null
|
||||
```
|
||||
|
||||
after `previous` moves to Node 2.
|
||||
|
||||
---
|
||||
|
||||
## Step 7 — Second Iteration: Move Pointers
|
||||
|
||||
```cpp
|
||||
previous = current;
|
||||
current = next;
|
||||
```
|
||||
|
||||
### Stack
|
||||
|
||||
```text
|
||||
+----------+----------+
|
||||
| Variable | Value |
|
||||
+----------+----------+
|
||||
| head | 0x1000 |
|
||||
| previous | 0x2000 |
|
||||
| current | 0x3000 |
|
||||
| next | 0x3000 |
|
||||
+----------+----------+
|
||||
```
|
||||
|
||||
### Heap
|
||||
|
||||
```text
|
||||
0x1000
|
||||
+-----------+-----------+
|
||||
| value = 1 | next=null |
|
||||
+-----------+-----------+
|
||||
|
||||
0x2000
|
||||
+-----------+-----------+
|
||||
| value = 2 | next=1000 |
|
||||
+-----------+-----------+
|
||||
|
||||
0x3000
|
||||
+-----------+-----------+
|
||||
| value = 3 | next=null |
|
||||
+-----------+-----------+
|
||||
```
|
||||
|
||||
### Logical view
|
||||
|
||||
```text
|
||||
previous
|
||||
|
|
||||
v
|
||||
2 -> 1 -> null
|
||||
|
||||
current
|
||||
|
|
||||
v
|
||||
3 -> null
|
||||
```
|
||||
|
||||
Now two nodes are reversed.
|
||||
|
||||
---
|
||||
|
||||
## Step 8 — Third Iteration: Save `next`
|
||||
|
||||
```cpp
|
||||
Node* next = current->next;
|
||||
```
|
||||
|
||||
`current` is Node 3.
|
||||
Node 3 points to `nullptr`.
|
||||
|
||||
So:
|
||||
|
||||
```text
|
||||
next = nullptr
|
||||
```
|
||||
|
||||
### Stack
|
||||
|
||||
```text
|
||||
+----------+----------+
|
||||
| Variable | Value |
|
||||
+----------+----------+
|
||||
| head | 0x1000 |
|
||||
| previous | 0x2000 |
|
||||
| current | 0x3000 |
|
||||
| next | nullptr |
|
||||
+----------+----------+
|
||||
```
|
||||
|
||||
### Heap
|
||||
|
||||
```text
|
||||
0x1000
|
||||
+-----------+-----------+
|
||||
| value = 1 | next=null |
|
||||
+-----------+-----------+
|
||||
|
||||
0x2000
|
||||
+-----------+-----------+
|
||||
| value = 2 | next=1000 |
|
||||
+-----------+-----------+
|
||||
|
||||
0x3000
|
||||
+-----------+-----------+
|
||||
| value = 3 | next=null |
|
||||
+-----------+-----------+
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Step 9 — Third Iteration: Reverse Link
|
||||
|
||||
```cpp
|
||||
current->next = previous;
|
||||
```
|
||||
|
||||
`current` is Node 3.
|
||||
`previous` is Node 2.
|
||||
|
||||
So Node 3 now points to Node 2.
|
||||
|
||||
### Stack
|
||||
|
||||
```text
|
||||
+----------+----------+
|
||||
| Variable | Value |
|
||||
+----------+----------+
|
||||
| head | 0x1000 |
|
||||
| previous | 0x2000 |
|
||||
| current | 0x3000 |
|
||||
| next | nullptr |
|
||||
+----------+----------+
|
||||
```
|
||||
|
||||
### Heap
|
||||
|
||||
```text
|
||||
0x1000
|
||||
+-----------+-----------+
|
||||
| value = 1 | next=null |
|
||||
+-----------+-----------+
|
||||
|
||||
0x2000
|
||||
+-----------+-----------+
|
||||
| value = 2 | next=1000 |
|
||||
+-----------+-----------+
|
||||
|
||||
0x3000
|
||||
+-----------+-----------+
|
||||
| value = 3 | next=2000 |
|
||||
+-----------+-----------+
|
||||
```
|
||||
|
||||
### Logical view
|
||||
|
||||
```text
|
||||
current
|
||||
|
|
||||
v
|
||||
3 -> 2 -> 1 -> null
|
||||
```
|
||||
|
||||
The whole list is now reversed, but the loop still needs to update the stack pointers.
|
||||
|
||||
---
|
||||
|
||||
## Step 10 — Third Iteration: Move Pointers
|
||||
|
||||
```cpp
|
||||
previous = current;
|
||||
current = next;
|
||||
```
|
||||
|
||||
Since `next` is `nullptr`, `current` becomes `nullptr`.
|
||||
|
||||
### Stack
|
||||
|
||||
```text
|
||||
+----------+----------+
|
||||
| Variable | Value |
|
||||
+----------+----------+
|
||||
| head | 0x1000 |
|
||||
| previous | 0x3000 |
|
||||
| current | nullptr |
|
||||
| next | nullptr |
|
||||
+----------+----------+
|
||||
```
|
||||
|
||||
### Heap
|
||||
|
||||
```text
|
||||
0x1000
|
||||
+-----------+-----------+
|
||||
| value = 1 | next=null |
|
||||
+-----------+-----------+
|
||||
|
||||
0x2000
|
||||
+-----------+-----------+
|
||||
| value = 2 | next=1000 |
|
||||
+-----------+-----------+
|
||||
|
||||
0x3000
|
||||
+-----------+-----------+
|
||||
| value = 3 | next=2000 |
|
||||
+-----------+-----------+
|
||||
```
|
||||
|
||||
### Logical view
|
||||
|
||||
```text
|
||||
previous
|
||||
|
|
||||
v
|
||||
3 -> 2 -> 1 -> null
|
||||
|
||||
current -> null
|
||||
```
|
||||
|
||||
The loop condition fails:
|
||||
|
||||
```cpp
|
||||
while(current != nullptr)
|
||||
```
|
||||
|
||||
because `current` is now `nullptr`.
|
||||
|
||||
---
|
||||
|
||||
## Step 11 — Return New Head
|
||||
|
||||
At the end:
|
||||
|
||||
```cpp
|
||||
return previous;
|
||||
```
|
||||
|
||||
`previous` points to Node 3.
|
||||
|
||||
Node 3 is the new head of the reversed list.
|
||||
|
||||
### Final stack view
|
||||
|
||||
```text
|
||||
+----------+----------+
|
||||
| Variable | Value |
|
||||
+----------+----------+
|
||||
| old head | 0x1000 |
|
||||
| new head | 0x3000 |
|
||||
+----------+----------+
|
||||
```
|
||||
|
||||
### Final heap view
|
||||
|
||||
```text
|
||||
0x3000
|
||||
+-----------+-----------+
|
||||
| value = 3 | next=2000 |
|
||||
+-----------+-----------+
|
||||
|
||||
0x2000
|
||||
+-----------+-----------+
|
||||
| value = 2 | next=1000 |
|
||||
+-----------+-----------+
|
||||
|
||||
0x1000
|
||||
+-----------+-----------+
|
||||
| value = 1 | next=null |
|
||||
+-----------+-----------+
|
||||
```
|
||||
|
||||
### Final logical view
|
||||
|
||||
```text
|
||||
new head
|
||||
|
|
||||
v
|
||||
3 -> 2 -> 1 -> null
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Why the Temporary `next` Pointer Matters
|
||||
|
||||
This line is not optional:
|
||||
|
||||
```cpp
|
||||
Node* next = current->next;
|
||||
```
|
||||
|
||||
Without it, this operation:
|
||||
|
||||
```cpp
|
||||
current->next = previous;
|
||||
```
|
||||
|
||||
would overwrite the only pointer to the remaining original list.
|
||||
|
||||
For example, at the beginning:
|
||||
|
||||
```text
|
||||
1 -> 2 -> 3 -> null
|
||||
```
|
||||
|
||||
If Node 1 is changed to:
|
||||
|
||||
```text
|
||||
1 -> null
|
||||
```
|
||||
|
||||
before saving Node 2, then Node 2 and Node 3 are no longer reachable from any local variable.
|
||||
|
||||
That is why the algorithm always follows this order:
|
||||
|
||||
```cpp
|
||||
Node* next = current->next; // preserve the remaining list
|
||||
current->next = previous; // reverse the link
|
||||
previous = current; // grow the reversed part
|
||||
current = next; // continue with the remaining part
|
||||
```
|
||||
|
||||
The order is the algorithm.
|
||||
|
||||
---
|
||||
|
||||
## Summary
|
||||
|
||||
During the algorithm:
|
||||
|
||||
- `previous` points to the already reversed part.
|
||||
- `current` points to the node being processed.
|
||||
- `next` preserves access to the not-yet-processed part.
|
||||
- Only one `next` field is modified per iteration.
|
||||
- No nodes are copied.
|
||||
- No new list is allocated.
|
||||
- The original nodes are relinked in-place.
|
||||
|
||||
The algorithm is small, but it is easy to get wrong because it mutates the structure while traversing it.
|
||||
|
||||
That is why a memory-level walkthrough is often more useful than just showing the final code.
|
||||
@@ -0,0 +1,5 @@
|
||||
Original list:
|
||||
1 -> 2 -> 3 -> 4 -> 5 -> null
|
||||
|
||||
Reversed list:
|
||||
5 -> 4 -> 3 -> 2 -> 1 -> null
|
||||
@@ -0,0 +1,7 @@
|
||||
TEMPLATE = app
|
||||
CONFIG += console c++17
|
||||
CONFIG -= app_bundle
|
||||
CONFIG -= qt
|
||||
|
||||
SOURCES += \
|
||||
main.cpp
|
||||
255
analysis/04-Reverse_Linked_List/readme.md
Normal file
255
analysis/04-Reverse_Linked_List/readme.md
Normal file
@@ -0,0 +1,255 @@
|
||||
# Analysis #04 — Reverse Linked List: Academic Exercise
|
||||
|
||||
## Problem
|
||||
|
||||
A classic interview question:
|
||||
|
||||
Given a singly linked list:
|
||||
|
||||
```cpp
|
||||
struct Node {
|
||||
int value;
|
||||
Node* next;
|
||||
};
|
||||
```
|
||||
|
||||
Reverse the list:
|
||||
|
||||
```text
|
||||
1 -> 2 -> 3 -> 4 -> null
|
||||
```
|
||||
|
||||
into:
|
||||
|
||||
```text
|
||||
4 -> 3 -> 2 -> 1 -> null
|
||||
```
|
||||
|
||||
using:
|
||||
|
||||
* O(n) time
|
||||
* O(1) additional memory
|
||||
|
||||
---
|
||||
|
||||
## Typical Interview Solution
|
||||
|
||||
The standard solution uses three pointers:
|
||||
|
||||
```cpp
|
||||
Node* previous = nullptr;
|
||||
Node* current = head;
|
||||
|
||||
while(current) {
|
||||
Node* next = current->next;
|
||||
|
||||
current->next = previous;
|
||||
|
||||
previous = current;
|
||||
current = next;
|
||||
}
|
||||
|
||||
head = previous;
|
||||
```
|
||||
|
||||
The candidate is expected to produce this solution quickly and correctly.
|
||||
|
||||
---
|
||||
|
||||
## What This Actually Tests
|
||||
|
||||
Despite its popularity, this problem tests a surprisingly narrow set of skills.
|
||||
|
||||
Primarily:
|
||||
|
||||
* Pointer manipulation
|
||||
* Attention to detail
|
||||
* Familiarity with linked lists
|
||||
* Prior exposure to a common interview pattern
|
||||
|
||||
In many cases, prior exposure matters more than reasoning.
|
||||
|
||||
A candidate who has seen the problem twenty times may solve it in under a minute.
|
||||
|
||||
A senior engineer with years of production experience may need significantly longer if they have never encountered this specific exercise before.
|
||||
|
||||
---
|
||||
|
||||
## Why This Is Rare In Real Engineering
|
||||
|
||||
The interesting question is:
|
||||
|
||||
> When was the last time you actually reversed a linked list in production code?
|
||||
|
||||
For most engineers, the answer is:
|
||||
|
||||
> Almost never.
|
||||
|
||||
Modern systems rarely use linked lists as a primary data structure.
|
||||
|
||||
More commonly you will encounter:
|
||||
|
||||
* vectors
|
||||
* deques
|
||||
* ring buffers
|
||||
* hash tables
|
||||
* trees
|
||||
* databases
|
||||
* message queues
|
||||
|
||||
The embedded world is similar.
|
||||
|
||||
Typical structures include:
|
||||
|
||||
* circular buffers
|
||||
* DMA buffers
|
||||
* message queues
|
||||
* routing tables
|
||||
* state machines
|
||||
|
||||
Linked lists certainly exist.
|
||||
|
||||
However, fully reversing one is rarely a real business requirement.
|
||||
|
||||
---
|
||||
|
||||
## The Hidden Assumption
|
||||
|
||||
The interview question starts with an assumption:
|
||||
|
||||
> You already have a linked list.
|
||||
|
||||
Real engineering often starts with a different question:
|
||||
|
||||
> Why is this a linked list in the first place?
|
||||
|
||||
That decision is usually far more important than the reversal algorithm itself.
|
||||
|
||||
---
|
||||
|
||||
## Real-World Equivalent
|
||||
|
||||
Finding a true production equivalent is difficult.
|
||||
|
||||
Most real systems solve a different problem.
|
||||
|
||||
### Example 1: Event History Viewer
|
||||
|
||||
A user wants to see the newest events first.
|
||||
|
||||
A typical engineering solution is:
|
||||
|
||||
* iterate in reverse
|
||||
* change presentation logic
|
||||
* adjust query ordering
|
||||
|
||||
The underlying data structure often remains unchanged.
|
||||
|
||||
---
|
||||
|
||||
### Example 2: CAN Trace Analysis
|
||||
|
||||
Suppose a trace contains millions of CAN frames.
|
||||
|
||||
The user wants the newest messages displayed at the top.
|
||||
|
||||
Nobody reverses the entire dataset.
|
||||
|
||||
Instead:
|
||||
|
||||
* reverse iteration is used
|
||||
* the UI changes presentation order
|
||||
* indexing structures provide efficient access
|
||||
|
||||
The stored data remains exactly as it was.
|
||||
|
||||
---
|
||||
|
||||
## What Real Engineers Usually Ask
|
||||
|
||||
A more practical engineering question would be:
|
||||
|
||||
> The user wants to view data in reverse order.
|
||||
>
|
||||
> Do we actually need to modify the data structure?
|
||||
|
||||
This question frequently leads to better solutions.
|
||||
|
||||
---
|
||||
|
||||
## Better Interview Question
|
||||
|
||||
Instead of asking:
|
||||
|
||||
> Reverse a linked list.
|
||||
|
||||
Consider asking:
|
||||
|
||||
> A system stores ten million records.
|
||||
>
|
||||
> Users want to view them in reverse order.
|
||||
>
|
||||
> What solution options exist, and what are their trade-offs?
|
||||
|
||||
Now the discussion becomes much more interesting:
|
||||
|
||||
* memory usage
|
||||
* cache locality
|
||||
* ownership
|
||||
* indexing
|
||||
* performance
|
||||
* maintainability
|
||||
* user requirements
|
||||
|
||||
In other words:
|
||||
|
||||
Engineering begins.
|
||||
|
||||
---
|
||||
|
||||
## Common Mistakes
|
||||
|
||||
* ❌ Assuming data must be modified to change presentation order
|
||||
* ❌ Ignoring alternative data structures
|
||||
* ❌ Focusing on implementation before understanding requirements
|
||||
* ❌ Treating algorithmic manipulation as the only valid solution
|
||||
|
||||
---
|
||||
|
||||
## Key Takeaway
|
||||
|
||||
Reverse Linked List is useful as an educational exercise.
|
||||
|
||||
It teaches pointer manipulation and careful reasoning about memory.
|
||||
|
||||
However, the problem itself rarely appears in production software in its original form.
|
||||
|
||||
The real engineering question is usually not:
|
||||
|
||||
> How do we reverse the list?
|
||||
|
||||
Instead it is:
|
||||
|
||||
> Do we need to reverse it at all?
|
||||
|
||||
---
|
||||
|
||||
## Project Perspective
|
||||
|
||||
> Exists in real engineering?
|
||||
|
||||
**Partially**
|
||||
|
||||
Linked lists exist.
|
||||
|
||||
Complete list reversal is a very uncommon business requirement.
|
||||
|
||||
> Exists in interview form?
|
||||
|
||||
**Yes**
|
||||
|
||||
It remains one of the most common classic coding interview questions.
|
||||
|
||||
The exercise is valuable for learning pointer manipulation.
|
||||
|
||||
Its usefulness as a predictor of engineering ability is far less obvious.
|
||||
Reference in New Issue
Block a user