Skip to content

Commit 1858151

Browse files
committed
feat(visualizations): add sort linked list algorithm visualization
Add interactive merge sort visualization for singly linked lists. The visualization demonstrates the slow/fast pointer technique to find the middle node, followed by recursive splitting and merging of sublists. The implementation uses an operation queue pattern to step through the recursive merge sort algorithm, showing each phase with color-coded node states. A new Linked List category has been added to the landing page with an SVG illustration depicting the sort transformation. The algorithm implementation includes Python dunder methods with @total_ordering decorator to enable direct node comparisons using comparison operators instead of explicit value access. Signed-off-by: Ayush Joshi <ayush854032@gmail.com>
1 parent d96f845 commit 1858151

6 files changed

Lines changed: 1186 additions & 0 deletions

File tree

algos/sorting/sort_linked_list.py

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
"""Given the head of a linked list, return the list after sorting it in ascending order."""
2+
3+
from functools import total_ordering
4+
from typing import Optional
5+
6+
7+
# Definition for singly-linked list.
8+
@total_ordering
9+
class ListNode:
10+
def __init__(self, val: int = 0, next: Optional["ListNode"] = None):
11+
self.val = val
12+
self.next = next
13+
14+
def __eq__(self, other: Optional["ListNode"] = None) -> bool:
15+
return self.val == other.val if other else False
16+
17+
def __lt__(self, other: Optional["ListNode"] = None) -> bool:
18+
return self.val < other.val if other else False
19+
20+
def to_list(head: "ListNode") -> list:
21+
result = []
22+
while head:
23+
result.append(head.val)
24+
head = head.next
25+
return result
26+
27+
28+
class Solution:
29+
def sortList(self, head: Optional[ListNode]) -> Optional[ListNode]:
30+
# Base case: if list is empty or has only one node
31+
if not head or not head.next:
32+
# Single node is always sorted
33+
return head
34+
35+
mid = self._getMid(head)
36+
left = head
37+
right = mid.next
38+
39+
# Crucial: break the link between halves, this is
40+
# the divide step
41+
mid.next = None
42+
43+
left_sorted = self.sortList(left)
44+
right_sorted = self.sortList(right)
45+
return self._merge(left_sorted, right_sorted)
46+
47+
def _getMid(self, head: ListNode) -> ListNode:
48+
"""Finds the middle node using Slow and Fast pointers."""
49+
slow, fast = head, head.next
50+
while fast and fast.next:
51+
slow = slow.next
52+
# Move `fast` 2x the speed of slow so that at the
53+
# end of the nodes `slow` will exactly be at the
54+
# half of the linked list
55+
fast = fast.next.next
56+
return slow
57+
58+
def _merge(
59+
self, left: Optional[ListNode], right: Optional[ListNode]
60+
) -> Optional[ListNode]:
61+
"""Standard merge logic for two sorted linked lists."""
62+
dummy = ListNode()
63+
tail = dummy
64+
65+
while left and right:
66+
if left < right:
67+
tail.next = left
68+
left = left.next
69+
else:
70+
tail.next = right
71+
right = right.next
72+
tail = tail.next
73+
74+
# Attach any remaining nodes
75+
tail.next = left or right
76+
return dummy.next
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import pytest
2+
3+
from algos.sorting.sort_linked_list import Solution, ListNode
4+
5+
6+
@pytest.fixture
7+
def solution() -> Solution:
8+
return Solution()
9+
10+
11+
def test_sort_linked_list(solution: Solution) -> None:
12+
values = [4, 2, 1, 3, 5, 6]
13+
head = None
14+
prev = None
15+
for v in values:
16+
node = ListNode(v)
17+
if prev:
18+
prev.next = node
19+
prev = node
20+
else:
21+
prev = node
22+
head = prev
23+
assert solution.sortList(head).to_list() == [1, 2, 3, 4, 5, 6]

visualizations/index.html

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -460,6 +460,62 @@ <h3>Binary Matrix Shortest Path</h3>
460460
</a>
461461
</div>
462462
</div>
463+
464+
<!-- Linked List -->
465+
<div class="category">
466+
<h2><span class="category-icon"></span> Linked List</h2>
467+
<div class="algorithms">
468+
<a href="linked-list/sort-linked-list/index.html" class="algo-card">
469+
<div class="algo-content">
470+
<h3>Sort Linked List</h3>
471+
<p>Sort a linked list using merge sort with O(n log n) time complexity.</p>
472+
<span class="algo-tag">Merge Sort</span>
473+
</div>
474+
<div class="algo-svg">
475+
<svg viewBox="0 0 80 80" fill="none" xmlns="http://www.w3.org/2000/svg">
476+
<!-- Unsorted linked list on top -->
477+
<rect x="5" y="12" width="14" height="14" rx="3" fill="#ff6b6b"/>
478+
<text x="12" y="22" fill="#fff" font-size="8" text-anchor="middle">4</text>
479+
<path d="M19 19 L24 19" stroke="#555" stroke-width="1.5"/>
480+
<rect x="24" y="12" width="14" height="14" rx="3" fill="#5b8def"/>
481+
<text x="31" y="22" fill="#fff" font-size="8" text-anchor="middle">2</text>
482+
<path d="M38 19 L43 19" stroke="#555" stroke-width="1.5"/>
483+
<rect x="43" y="12" width="14" height="14" rx="3" fill="#ffd700"/>
484+
<text x="50" y="22" fill="#1a1a2e" font-size="8" text-anchor="middle">1</text>
485+
<path d="M57 19 L62 19" stroke="#555" stroke-width="1.5"/>
486+
<rect x="62" y="12" width="14" height="14" rx="3" fill="#ff6b6b"/>
487+
<text x="69" y="22" fill="#fff" font-size="8" text-anchor="middle">3</text>
488+
<!-- Arrow down -->
489+
<path d="M40 30 L40 38" stroke="#00d9ff" stroke-width="2"/>
490+
<path d="M36 35 L40 40 L44 35" stroke="#00d9ff" stroke-width="2" fill="none"/>
491+
<!-- Split visualization -->
492+
<rect x="10" y="45" width="12" height="12" rx="2" fill="#5b8def" class="animate-on-hover"/>
493+
<text x="16" y="54" fill="#fff" font-size="7" text-anchor="middle">4</text>
494+
<rect x="25" y="45" width="12" height="12" rx="2" fill="#5b8def"/>
495+
<text x="31" y="54" fill="#fff" font-size="7" text-anchor="middle">2</text>
496+
<text x="21" y="42" fill="#5b8def" font-size="6">L</text>
497+
<rect x="43" y="45" width="12" height="12" rx="2" fill="#ffd700"/>
498+
<text x="49" y="54" fill="#1a1a2e" font-size="7" text-anchor="middle">1</text>
499+
<rect x="58" y="45" width="12" height="12" rx="2" fill="#ffd700" class="animate-on-hover"/>
500+
<text x="64" y="54" fill="#1a1a2e" font-size="7" text-anchor="middle">3</text>
501+
<text x="56" y="42" fill="#ffd700" font-size="6">R</text>
502+
<!-- Sorted result -->
503+
<rect x="5" y="62" width="14" height="14" rx="3" fill="#4ecdc4"/>
504+
<text x="12" y="72" fill="#1a1a2e" font-size="8" text-anchor="middle">1</text>
505+
<path d="M19 69 L24 69" stroke="#4ecdc4" stroke-width="1.5"/>
506+
<rect x="24" y="62" width="14" height="14" rx="3" fill="#4ecdc4"/>
507+
<text x="31" y="72" fill="#1a1a2e" font-size="8" text-anchor="middle">2</text>
508+
<path d="M38 69 L43 69" stroke="#4ecdc4" stroke-width="1.5"/>
509+
<rect x="43" y="62" width="14" height="14" rx="3" fill="#4ecdc4"/>
510+
<text x="50" y="72" fill="#1a1a2e" font-size="8" text-anchor="middle">3</text>
511+
<path d="M57 69 L62 69" stroke="#4ecdc4" stroke-width="1.5"/>
512+
<rect x="62" y="62" width="14" height="14" rx="3" fill="#4ecdc4"/>
513+
<text x="69" y="72" fill="#1a1a2e" font-size="8" text-anchor="middle">4</text>
514+
</svg>
515+
</div>
516+
</a>
517+
</div>
518+
</div>
463519
</div>
464520

465521
<div class="footer">
Lines changed: 209 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,209 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="UTF-8">
5+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
6+
<title>Sort Linked List - Merge Sort</title>
7+
<link rel="stylesheet" href="../../shared/css/main.css">
8+
<link rel="stylesheet" href="style.css">
9+
</head>
10+
<body>
11+
<div class="container">
12+
<a href="../../" class="back-link">&larr; Back to all algorithms</a>
13+
<h1>Sort Linked List</h1>
14+
<p class="description">Sort a linked list in ascending order using merge sort with O(n log n) time complexity</p>
15+
16+
<div class="controls">
17+
<div class="input-group">
18+
<label>Linked List:</label>
19+
<input type="text" id="listInput" value="4, 2, 1, 3, 5, 6" placeholder="e.g., 4, 2, 1, 3">
20+
</div>
21+
<button class="btn-random" id="randomBtn">Random</button>
22+
<button class="btn-primary" id="resetBtn">Reset</button>
23+
<button class="btn-secondary" id="stepBtn">Step</button>
24+
<button class="btn-success" id="playBtn">Play</button>
25+
<div class="speed-control">
26+
<label>Speed:</label>
27+
<input type="range" id="speedSlider" min="100" max="2000" value="800">
28+
</div>
29+
</div>
30+
31+
<div class="main-content">
32+
<div class="visualization">
33+
<div class="section-title">Linked List</div>
34+
35+
<div class="linked-list-wrapper" id="listWrapper">
36+
<div class="linked-list-container" id="listDisplay"></div>
37+
</div>
38+
39+
<div class="info-panel">
40+
<div class="info-box phase-box">
41+
<h3>Phase</h3>
42+
<div class="info-value" id="phaseValue">-</div>
43+
<div class="pointer-desc">Current operation</div>
44+
</div>
45+
<div class="info-box slow-box">
46+
<h3>Slow Ptr</h3>
47+
<div class="info-value" id="slowValue">-</div>
48+
<div class="pointer-desc">Finds middle</div>
49+
</div>
50+
<div class="info-box fast-box">
51+
<h3>Fast Ptr</h3>
52+
<div class="info-value" id="fastValue">-</div>
53+
<div class="pointer-desc">Moves 2x speed</div>
54+
</div>
55+
<div class="info-box depth-box">
56+
<h3>Depth</h3>
57+
<div class="info-value" id="depthValue">0</div>
58+
<div class="pointer-desc">Recursion level</div>
59+
</div>
60+
</div>
61+
62+
<div class="merge-info" id="mergeInfo" style="display: none;">
63+
<div class="merge-title">Merging Two Sorted Lists</div>
64+
<div class="merge-pointers">
65+
<div class="merge-ptr left-merge-ptr">
66+
<span class="ptr-label">Left:</span>
67+
<span class="ptr-value" id="leftPtrValue">-</span>
68+
</div>
69+
<div class="merge-ptr right-merge-ptr">
70+
<span class="ptr-label">Right:</span>
71+
<span class="ptr-value" id="rightPtrValue">-</span>
72+
</div>
73+
</div>
74+
</div>
75+
76+
<div class="status" id="status">
77+
Click "Step" or "Play" to start sorting the linked list
78+
</div>
79+
80+
<div class="legend">
81+
<div class="legend-item">
82+
<div class="legend-color default-color"></div>
83+
<span>Unsorted</span>
84+
</div>
85+
<div class="legend-item">
86+
<div class="legend-color finding-mid-color"></div>
87+
<span>Finding Mid</span>
88+
</div>
89+
<div class="legend-item">
90+
<div class="legend-color split-color"></div>
91+
<span>Split Point</span>
92+
</div>
93+
<div class="legend-item">
94+
<div class="legend-color comparing-color"></div>
95+
<span>Comparing</span>
96+
</div>
97+
<div class="legend-item">
98+
<div class="legend-color selected-color"></div>
99+
<span>Selected</span>
100+
</div>
101+
<div class="legend-item">
102+
<div class="legend-color sorted-color"></div>
103+
<span>Sorted</span>
104+
</div>
105+
</div>
106+
107+
<div class="pointer-legend">
108+
<div class="pointer-item slow">&#9660; slow - moves 1 step</div>
109+
<div class="pointer-item fast">&#9660; fast - moves 2 steps</div>
110+
</div>
111+
</div>
112+
113+
<div class="code-panel">
114+
<div class="section-title">Python Code - Merge Sort (Linked List)</div>
115+
<div class="code-line" id="code-0">
116+
<span class="code-keyword">def</span> <span class="code-function">sortList</span>(head):
117+
</div>
118+
<div class="code-line" id="code-1">
119+
&nbsp;&nbsp;&nbsp;&nbsp;<span class="code-keyword">if not</span> head <span class="code-keyword">or not</span> head.next:
120+
</div>
121+
<div class="code-line" id="code-2">
122+
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="code-keyword">return</span> head
123+
</div>
124+
<div class="code-line" id="code-3">
125+
&nbsp;&nbsp;&nbsp;&nbsp;mid = getMid(head)
126+
</div>
127+
<div class="code-line" id="code-4">
128+
&nbsp;&nbsp;&nbsp;&nbsp;left = head
129+
</div>
130+
<div class="code-line" id="code-5">
131+
&nbsp;&nbsp;&nbsp;&nbsp;right = mid.next
132+
</div>
133+
<div class="code-line" id="code-6">
134+
&nbsp;&nbsp;&nbsp;&nbsp;mid.next = <span class="code-keyword">None</span> <span class="code-comment"># Split the list</span>
135+
</div>
136+
<div class="code-line" id="code-7">
137+
&nbsp;&nbsp;&nbsp;&nbsp;left_sorted = sortList(left)
138+
</div>
139+
<div class="code-line" id="code-8">
140+
&nbsp;&nbsp;&nbsp;&nbsp;right_sorted = sortList(right)
141+
</div>
142+
<div class="code-line" id="code-9">
143+
&nbsp;&nbsp;&nbsp;&nbsp;<span class="code-keyword">return</span> merge(left_sorted, right_sorted)
144+
</div>
145+
<div class="code-line" id="code-10">
146+
&nbsp;
147+
</div>
148+
<div class="code-line" id="code-11">
149+
<span class="code-keyword">def</span> <span class="code-function">getMid</span>(head): <span class="code-comment"># Slow/Fast pointers</span>
150+
</div>
151+
<div class="code-line" id="code-12">
152+
&nbsp;&nbsp;&nbsp;&nbsp;slow, fast = head, head.next
153+
</div>
154+
<div class="code-line" id="code-13">
155+
&nbsp;&nbsp;&nbsp;&nbsp;<span class="code-keyword">while</span> fast <span class="code-keyword">and</span> fast.next:
156+
</div>
157+
<div class="code-line" id="code-14">
158+
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;slow = slow.next
159+
</div>
160+
<div class="code-line" id="code-15">
161+
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;fast = fast.next.next
162+
</div>
163+
<div class="code-line" id="code-16">
164+
&nbsp;&nbsp;&nbsp;&nbsp;<span class="code-keyword">return</span> slow
165+
</div>
166+
<div class="code-line" id="code-17">
167+
&nbsp;
168+
</div>
169+
<div class="code-line" id="code-18">
170+
<span class="code-keyword">def</span> <span class="code-function">merge</span>(l1, l2): <span class="code-comment"># Merge two sorted lists</span>
171+
</div>
172+
<div class="code-line" id="code-19">
173+
&nbsp;&nbsp;&nbsp;&nbsp;dummy = ListNode()
174+
</div>
175+
<div class="code-line" id="code-20">
176+
&nbsp;&nbsp;&nbsp;&nbsp;tail = dummy
177+
</div>
178+
<div class="code-line" id="code-21">
179+
&nbsp;&nbsp;&nbsp;&nbsp;<span class="code-keyword">while</span> l1 <span class="code-keyword">and</span> l2:
180+
</div>
181+
<div class="code-line" id="code-22">
182+
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="code-keyword">if</span> l1.val &lt; l2.val:
183+
</div>
184+
<div class="code-line" id="code-23">
185+
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;tail.next = l1; l1 = l1.next
186+
</div>
187+
<div class="code-line" id="code-24">
188+
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="code-keyword">else</span>:
189+
</div>
190+
<div class="code-line" id="code-25">
191+
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;tail.next = l2; l2 = l2.next
192+
</div>
193+
<div class="code-line" id="code-26">
194+
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;tail = tail.next
195+
</div>
196+
<div class="code-line" id="code-27">
197+
&nbsp;&nbsp;&nbsp;&nbsp;tail.next = l1 <span class="code-keyword">or</span> l2
198+
</div>
199+
<div class="code-line" id="code-28">
200+
&nbsp;&nbsp;&nbsp;&nbsp;<span class="code-keyword">return</span> dummy.next
201+
</div>
202+
</div>
203+
</div>
204+
</div>
205+
206+
<script src="../../shared/js/visualizer.js"></script>
207+
<script src="script.js"></script>
208+
</body>
209+
</html>

0 commit comments

Comments
 (0)