adding heap sort animation
This commit is contained in:
committed by
JamesFlare1212
parent
8e49abd24a
commit
425b39cf7f
439
animations/heap/sort/heap-sort.js
Normal file
439
animations/heap/sort/heap-sort.js
Normal file
@@ -0,0 +1,439 @@
|
||||
//AUTHOR: Chloe Lee
|
||||
|
||||
// initial array to be sorted
|
||||
let array = [5, 3, 8, 1, 9, 4];
|
||||
let currentStep = 0;
|
||||
let animationSteps = [];
|
||||
|
||||
// heap sort code
|
||||
const codeLines = [
|
||||
"void heapSort(int arr[], int n) {",
|
||||
" for (int i = n / 2 - 1; i >= 0; i--)",
|
||||
" heapify(arr, n, i);",
|
||||
" for (int i = n - 1; i > 0; i--) {",
|
||||
" std::swap(arr[0], arr[i]);",
|
||||
" heapify(arr, i, 0);",
|
||||
" }",
|
||||
"}",
|
||||
"",
|
||||
"void heapify(int arr[], int n, int i) {",
|
||||
" int largest = i;",
|
||||
" int left = 2 * i + 1;",
|
||||
" int right = 2 * i + 2;",
|
||||
" if (left < n && arr[left] > arr[largest])",
|
||||
" largest = left;",
|
||||
" if (right < n && arr[right] > arr[largest])",
|
||||
" largest = right;",
|
||||
" if (largest != i) {",
|
||||
" std::swap(arr[i], arr[largest]);",
|
||||
" heapify(arr, n, largest);",
|
||||
" }",
|
||||
"}"
|
||||
];
|
||||
|
||||
function initialize() {
|
||||
// Display code with line numbers
|
||||
const codeElement = document.getElementById('code');
|
||||
let codeHTML = '';
|
||||
|
||||
codeLines.forEach((line, index) => {
|
||||
// how much padding to add
|
||||
const paddingLeft = line.startsWith(' ') ? '20px' : '0';
|
||||
// add the line number for the code
|
||||
codeHTML += `<div id="code-line-${index}">`;
|
||||
|
||||
if (!line.trim()) {
|
||||
codeHTML += ' ';
|
||||
} else {
|
||||
const lineNum = index;
|
||||
// align single digit lines better
|
||||
const formattedLineNum = lineNum < 10 ? ` ${lineNum}` : lineNum;
|
||||
codeHTML += `${formattedLineNum}. ${line}`;
|
||||
}
|
||||
|
||||
codeHTML += '</div>';
|
||||
});
|
||||
|
||||
codeElement.innerHTML = codeHTML;
|
||||
generateAnimationSteps();
|
||||
// initial array display
|
||||
updateArrayDisplay();
|
||||
// initial heap tree
|
||||
drawHeapTree();
|
||||
|
||||
// set up next step buttons
|
||||
document.getElementById('arrayNextBtn').addEventListener('click', nextStep);
|
||||
document.getElementById('treeNextBtn').addEventListener('click', nextStep);
|
||||
}
|
||||
|
||||
// highlight a specific line of code
|
||||
function highlightLine(lineIndex) {
|
||||
// reset all lines
|
||||
for (let i = 0; i < codeLines.length; i++) {
|
||||
const line = document.getElementById(`code-line-${i}`);
|
||||
if (line) {
|
||||
line.style.backgroundColor = 'transparent';
|
||||
line.style.fontWeight = 'normal';
|
||||
}
|
||||
}
|
||||
|
||||
// highlight the specified line
|
||||
const highlightedLine = document.getElementById(`code-line-${lineIndex}`);
|
||||
if (highlightedLine) {
|
||||
highlightedLine.style.fontWeight = 'bold';
|
||||
}
|
||||
}
|
||||
|
||||
// update the array display
|
||||
function updateArrayDisplay() {
|
||||
const arrayContainer = document.getElementById('arrayContainer');
|
||||
const descriptionElement = document.getElementById('arrayDescription');
|
||||
arrayContainer.innerHTML = '';
|
||||
|
||||
// create array visualization
|
||||
array.forEach((value, index) => {
|
||||
const elementContainer = document.createElement('div');
|
||||
elementContainer.style.display = 'inline-block';
|
||||
elementContainer.style.marginRight = '5px';
|
||||
|
||||
const element = document.createElement('div');
|
||||
element.className = 'array-element';
|
||||
|
||||
// highlight right lines, and nodes based on current step
|
||||
if (animationSteps[currentStep] && animationSteps[currentStep].comparing && animationSteps[currentStep].comparing.includes(index)) {
|
||||
element.classList.add('highlight');
|
||||
} else if (animationSteps[currentStep] && animationSteps[currentStep].swapping && animationSteps[currentStep].swapping.includes(index)) {
|
||||
element.classList.add('swap');
|
||||
} else if (animationSteps[currentStep] && animationSteps[currentStep].sorted && animationSteps[currentStep].sorted.includes(index)) {
|
||||
element.classList.add('done');
|
||||
}
|
||||
|
||||
element.textContent = value;
|
||||
elementContainer.appendChild(element);
|
||||
|
||||
// add index below
|
||||
const indexElement = document.createElement('div');
|
||||
indexElement.className = 'array-index';
|
||||
indexElement.textContent = index;
|
||||
elementContainer.appendChild(indexElement);
|
||||
|
||||
arrayContainer.appendChild(elementContainer);
|
||||
});
|
||||
|
||||
// update the description
|
||||
if (animationSteps[currentStep] && animationSteps[currentStep].description) {
|
||||
descriptionElement.textContent = animationSteps[currentStep].description;
|
||||
} else {
|
||||
descriptionElement.textContent = '';
|
||||
}
|
||||
}
|
||||
|
||||
// draw heap tree on the canvas
|
||||
function drawHeapTree() {
|
||||
const canvas = document.getElementById('treeCanvas');
|
||||
// canvas with context
|
||||
const ctx = canvas.getContext('2d');
|
||||
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
||||
const heapSize = animationSteps[currentStep] ? (animationSteps[currentStep].heapSize || array.length) : array.length;
|
||||
|
||||
// tree node positions (x, y coordinates for each node)
|
||||
const positions = [
|
||||
[250, 50], // index 0
|
||||
[150, 120], // index 1
|
||||
[350, 120], // index 2
|
||||
[100, 190], // index 3
|
||||
[200, 190], // index 4
|
||||
[300, 190], // index 5
|
||||
];
|
||||
|
||||
// draw edges/arrows
|
||||
for (let i = 0; i < Math.min(array.length, positions.length); i++) {
|
||||
if (i > 0 && i < heapSize) {
|
||||
const parentIndex = Math.floor((i - 1) / 2);
|
||||
if (parentIndex < heapSize) {
|
||||
// starting point for the line from parent to child
|
||||
const startX = positions[parentIndex][0];
|
||||
// bottom of parent node
|
||||
const startY = positions[parentIndex][1] + 20;
|
||||
const endX = positions[i][0];
|
||||
// top of child node
|
||||
const endY = positions[i][1] - 20;
|
||||
|
||||
// draw the lines for the arrows
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(startX, startY);
|
||||
ctx.lineTo(endX, endY);
|
||||
ctx.stroke();
|
||||
|
||||
// draw arrows
|
||||
const angle = Math.atan2(endY - startY, endX - startX);
|
||||
const arrowSize = 10;
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(endX, endY);
|
||||
ctx.lineTo(
|
||||
endX - arrowSize * Math.cos(angle - Math.PI / 6),
|
||||
endY - arrowSize * Math.sin(angle - Math.PI / 6)
|
||||
);
|
||||
ctx.lineTo(
|
||||
endX - arrowSize * Math.cos(angle + Math.PI / 6),
|
||||
endY - arrowSize * Math.sin(angle + Math.PI / 6)
|
||||
);
|
||||
ctx.closePath();
|
||||
ctx.fillStyle = '#555';
|
||||
ctx.fill();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// !! subject to change !!
|
||||
for (let i = 0; i < Math.min(array.length, positions.length); i++) {
|
||||
if (i < heapSize) {
|
||||
// determine node color based on current operation
|
||||
let nodeColor = '#ddd';
|
||||
let borderColor = '#888';
|
||||
|
||||
if (animationSteps[currentStep] && animationSteps[currentStep].comparing &&
|
||||
animationSteps[currentStep].comparing.includes(i)) {
|
||||
nodeColor = '#ffcf4d';
|
||||
borderColor = '#e8a800';
|
||||
} else if (animationSteps[currentStep] && animationSteps[currentStep].swapping &&
|
||||
animationSteps[currentStep].swapping.includes(i)) {
|
||||
nodeColor = '#ff7675';
|
||||
borderColor = '#d63031';
|
||||
} else if (animationSteps[currentStep] && animationSteps[currentStep].sorted &&
|
||||
animationSteps[currentStep].sorted.includes(i)) {
|
||||
nodeColor = '#81ecec';
|
||||
borderColor = '#00cec9';
|
||||
}
|
||||
|
||||
// draw rectangle with round corners
|
||||
const width = 40;
|
||||
const height = 40;
|
||||
const x = positions[i][0] - width/2;
|
||||
const y = positions[i][1] - height/2;
|
||||
const radius = 5;
|
||||
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(x + radius, y);
|
||||
ctx.lineTo(x + width - radius, y);
|
||||
ctx.quadraticCurveTo(x + width, y, x + width, y + radius);
|
||||
ctx.lineTo(x + width, y + height - radius);
|
||||
ctx.quadraticCurveTo(x + width, y + height, x + width - radius, y + height);
|
||||
ctx.lineTo(x + radius, y + height);
|
||||
ctx.quadraticCurveTo(x, y + height, x, y + height - radius);
|
||||
ctx.lineTo(x, y + radius);
|
||||
ctx.quadraticCurveTo(x, y, x + radius, y);
|
||||
ctx.closePath();
|
||||
|
||||
// style rectangle
|
||||
ctx.fillStyle = nodeColor;
|
||||
ctx.fill();
|
||||
ctx.strokeStyle = borderColor;
|
||||
ctx.lineWidth = 2;
|
||||
ctx.stroke();
|
||||
|
||||
// draw text in the heap tree
|
||||
ctx.fillStyle = 'black';
|
||||
ctx.textAlign = 'center';
|
||||
ctx.textBaseline = 'middle';
|
||||
ctx.font = 'bold 16px Times New Roman';
|
||||
ctx.fillText(array[i].toString(), positions[i][0], positions[i][1]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function generateAnimationSteps() {
|
||||
animationSteps = [];
|
||||
let tempArray = [...array];
|
||||
const n = tempArray.length;
|
||||
animationSteps.push({
|
||||
array: [...tempArray],
|
||||
line: 0,
|
||||
description: 'Starting heap sort with array [' + tempArray.join(', ') + ']',
|
||||
heapSize: n
|
||||
});
|
||||
|
||||
// build max-heap
|
||||
animationSteps.push({
|
||||
array: [...tempArray],
|
||||
line: 2,
|
||||
description: 'Building max heap',
|
||||
heapSize: n
|
||||
});
|
||||
|
||||
let sortedIndices = [];
|
||||
|
||||
for (let i = Math.floor(n / 2) - 1; i >= 0; i--) {
|
||||
animationSteps.push({
|
||||
array: [...tempArray],
|
||||
line: 3,
|
||||
description: `Heapifying subtree rooted at index ${i}`,
|
||||
heapSize: n,
|
||||
comparing: [i]
|
||||
});
|
||||
|
||||
heapifyWithAnimation(tempArray, n, i, sortedIndices);
|
||||
}
|
||||
|
||||
animationSteps.push({
|
||||
array: [...tempArray],
|
||||
line: 6,
|
||||
description: 'Max heap built. Extracting elements one by one',
|
||||
heapSize: n
|
||||
});
|
||||
|
||||
for (let i = n - 1; i > 0; i--) {
|
||||
animationSteps.push({
|
||||
array: [...tempArray],
|
||||
line: 8,
|
||||
description: `Swapping root (${tempArray[0]}) with element at index ${i} (${tempArray[i]})`,
|
||||
heapSize: i + 1,
|
||||
swapping: [0, i]
|
||||
});
|
||||
|
||||
[tempArray[0], tempArray[i]] = [tempArray[i], tempArray[0]];
|
||||
|
||||
// this index is sorted
|
||||
sortedIndices.push(i);
|
||||
|
||||
animationSteps.push({
|
||||
array: [...tempArray],
|
||||
line: 8,
|
||||
description: `Element ${tempArray[i]} is now in its correct position`,
|
||||
heapSize: i,
|
||||
sorted: [...sortedIndices]
|
||||
});
|
||||
|
||||
animationSteps.push({
|
||||
array: [...tempArray],
|
||||
line: 11,
|
||||
description: `Heapifying reduced heap (size ${i})`,
|
||||
heapSize: i
|
||||
});
|
||||
|
||||
heapifyWithAnimation(tempArray, i, 0, sortedIndices);
|
||||
}
|
||||
|
||||
sortedIndices.push(0);
|
||||
animationSteps.push({
|
||||
array: [...tempArray],
|
||||
line: 13,
|
||||
description: 'Heap sort complete! Array is now sorted: [' + tempArray.join(', ') + ']',
|
||||
heapSize: n,
|
||||
sorted: sortedIndices
|
||||
});
|
||||
}
|
||||
|
||||
// heapify function that records animation steps
|
||||
function heapifyWithAnimation(arr, n, i, sortedIndices) {
|
||||
let largest = i;
|
||||
const left = 2 * i + 1;
|
||||
const right = 2 * i + 2;
|
||||
|
||||
animationSteps.push({
|
||||
array: [...arr],
|
||||
line: 16,
|
||||
description: `Heapifying at index ${i}`,
|
||||
heapSize: n,
|
||||
comparing: [i],
|
||||
sorted: sortedIndices ? [...sortedIndices] : []
|
||||
});
|
||||
|
||||
// compare with left child
|
||||
if (left < n) {
|
||||
animationSteps.push({
|
||||
array: [...arr],
|
||||
line: 21,
|
||||
description: `Comparing ${arr[i]} (index ${i}) with left child ${arr[left]} (index ${left})`,
|
||||
heapSize: n,
|
||||
comparing: [i, left],
|
||||
sorted: sortedIndices ? [...sortedIndices] : []
|
||||
});
|
||||
|
||||
if (arr[left] > arr[largest]) {
|
||||
largest = left;
|
||||
|
||||
animationSteps.push({
|
||||
array: [...arr],
|
||||
line: 22,
|
||||
description: `Left child ${arr[left]} is larger than current largest ${arr[i]}`,
|
||||
heapSize: n,
|
||||
comparing: [largest],
|
||||
sorted: sortedIndices ? [...sortedIndices] : []
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// compare with right child
|
||||
if (right < n) {
|
||||
animationSteps.push({
|
||||
array: [...arr],
|
||||
line: 25,
|
||||
description: `Comparing ${arr[largest]} (index ${largest}) with right child ${arr[right]} (index ${right})`,
|
||||
heapSize: n,
|
||||
comparing: [largest, right],
|
||||
sorted: sortedIndices ? [...sortedIndices] : []
|
||||
});
|
||||
|
||||
if (arr[right] > arr[largest]) {
|
||||
largest = right;
|
||||
|
||||
animationSteps.push({
|
||||
array: [...arr],
|
||||
line: 26,
|
||||
description: `Right child ${arr[right]} is larger than current largest ${arr[largest === i ? arr[i] : arr[left]]}`,
|
||||
heapSize: n,
|
||||
comparing: [largest],
|
||||
sorted: sortedIndices ? [...sortedIndices] : []
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// if largest is not root, swap and heapify
|
||||
if (largest !== i) {
|
||||
animationSteps.push({
|
||||
array: [...arr],
|
||||
line: 30,
|
||||
description: `Swapping ${arr[i]} (index ${i}) with ${arr[largest]} (index ${largest})`,
|
||||
heapSize: n,
|
||||
swapping: [i, largest],
|
||||
sorted: sortedIndices ? [...sortedIndices] : []
|
||||
});
|
||||
|
||||
[arr[i], arr[largest]] = [arr[largest], arr[i]];
|
||||
|
||||
animationSteps.push({
|
||||
array: [...arr],
|
||||
line: 33,
|
||||
description: `Recursively heapifying the affected subtree rooted at index ${largest}`,
|
||||
heapSize: n,
|
||||
comparing: [largest],
|
||||
sorted: sortedIndices ? [...sortedIndices] : []
|
||||
});
|
||||
|
||||
heapifyWithAnimation(arr, n, largest, sortedIndices);
|
||||
}
|
||||
}
|
||||
|
||||
function nextStep() {
|
||||
if (currentStep < animationSteps.length - 1) {
|
||||
currentStep++;
|
||||
|
||||
// update the array to current state
|
||||
if (animationSteps[currentStep] && animationSteps[currentStep].array) {
|
||||
array = [...animationSteps[currentStep].array];
|
||||
}
|
||||
|
||||
// highlight code line
|
||||
if (animationSteps[currentStep] && animationSteps[currentStep].line !== undefined) {
|
||||
highlightLine(animationSteps[currentStep].line);
|
||||
}
|
||||
|
||||
// update visuals
|
||||
updateArrayDisplay();
|
||||
drawHeapTree();
|
||||
}
|
||||
}
|
||||
|
||||
// initialize on page load
|
||||
window.onload = initialize;
|
||||
142
animations/heap/sort/index.html
Normal file
142
animations/heap/sort/index.html
Normal file
@@ -0,0 +1,142 @@
|
||||
<!-- AUTHOR: Chloe Lee -->
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Heap Sort Visualization</title>
|
||||
<script src="../../konva.js"></script>
|
||||
<style>
|
||||
body {
|
||||
font-family: 'Times New Roman', Times, serif, sans-serif;
|
||||
padding: 20px;
|
||||
background-color: white;
|
||||
}
|
||||
h1 {
|
||||
font-size: 24px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
p {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
.container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 30px;
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
.code-block {
|
||||
background-color: #e6e6e6;
|
||||
border-radius: 10px;
|
||||
padding: 20px;
|
||||
box-shadow: 5px 5px 10px rgba(0,0,0,0.2);
|
||||
width: 100%;
|
||||
max-width: 600px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
.visualization-container {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
gap: 20px;
|
||||
}
|
||||
.visualization-box {
|
||||
background-color: #e6e6e6;
|
||||
border-radius: 10px;
|
||||
padding: 20px;
|
||||
box-shadow: 5px 5px 10px rgba(0,0,0,0.2);
|
||||
flex: 1;
|
||||
min-height: 200px;
|
||||
position: relative;
|
||||
}
|
||||
button.next-step {
|
||||
font-family: 'Times New Roman', Times, serif, sans-serif;
|
||||
margin: 0;
|
||||
padding: 5px 10px;
|
||||
font-size: 14px;
|
||||
background-color: #f0f0f0;
|
||||
border: 1px solid #999;
|
||||
border-radius: 3px;
|
||||
cursor: pointer;
|
||||
position: absolute;
|
||||
left: 20px;
|
||||
top: 20px;
|
||||
}
|
||||
.array-container {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
margin-top: 60px;
|
||||
}
|
||||
.array-element {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
line-height: 40px;
|
||||
text-align: center;
|
||||
background-color: #ddd;
|
||||
border: 2px solid #888;
|
||||
border-radius: 5px;
|
||||
margin: 0 2px;
|
||||
font-weight: bold;
|
||||
}
|
||||
.array-index {
|
||||
text-align: center;
|
||||
margin-top: 5px;
|
||||
font-size: 14px;
|
||||
}
|
||||
.highlight {
|
||||
background-color: #ffcf4d;
|
||||
border-color: #e8a800;
|
||||
}
|
||||
.swap {
|
||||
background-color: #ff7675;
|
||||
border-color: #d63031;
|
||||
}
|
||||
.done {
|
||||
background-color: #81ecec;
|
||||
border-color: #00cec9;
|
||||
}
|
||||
.description {
|
||||
margin-top: 20px;
|
||||
text-align: center;
|
||||
font-size: 14px;
|
||||
height: 40px;
|
||||
}
|
||||
#code{
|
||||
margin: 0;
|
||||
font-family: monospace;
|
||||
font-size: 14px;
|
||||
}
|
||||
#treeCanvas{
|
||||
margin-top: 30px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<div>
|
||||
<h1>Heap Sort Visualization</h1>
|
||||
<p>This animation shows how the heap sort algorithm works by building a max heap and then extracting elements one by one. Click the "next step" button to run the animation.</p>
|
||||
</div>
|
||||
|
||||
<!-- block of C++ code-->
|
||||
<div class="code-block" id="code-block">
|
||||
<pre id="code"></pre>
|
||||
</div>
|
||||
|
||||
<div class="visualization-container">
|
||||
<!-- array goes here -->
|
||||
<div class="visualization-box">
|
||||
<button id="arrayNextBtn" class="next-step">Next Step</button>
|
||||
<div id="arrayContainer" class="array-container"></div>
|
||||
<div id="arrayDescription" class="description"></div>
|
||||
</div>
|
||||
|
||||
<!-- heap tree goes here -->
|
||||
<div class="visualization-box">
|
||||
<button id="treeNextBtn" class="next-step">Next Step</button>
|
||||
<canvas id="treeCanvas" width="500" height="300"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<script src="heap-sort.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user