Last active
July 12, 2018 17:52
-
-
Save LearningNerd/2526ee9c477977ffcf1d65db05089be5 to your computer and use it in GitHub Desktop.
Working with binary trees and drawing them (p5js canvas library)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
///////////////////////////////////////////////////////////////////////////////////// | |
// GENERATING AND DRAWING TREES OF n LEVELS -- for now, easy mode -- perfect binary trees | |
// Note: using p5js for drawing functions; didn't include p5js setup() here | |
///////////////////////////////////////////////////////////////////////////////////// | |
/////////////////////////////////////////////// | |
// SETUP / INITIALIZING VARIBALES | |
/////////////////////////////////////////////// | |
// Iterate step by step with mouse clicks | |
document.addEventListener("click", generateAndDrawTree); | |
// PARAMETER -- tree with n levels: | |
let numLevels = 4; | |
// Setup for tree traversal: | |
let totalNodes = 2**numLevels - 1; // NOTE: assuming the tree will always have a root node | |
let tree = {}; | |
let node = tree; | |
let count = 1; | |
let nextNodes = []; | |
// Setup for drawing: | |
const canvasWidth = 900, canvasHeight = 900; // canvas dimensions | |
const nodeSize = 50; // given as diameter of each node | |
const xMargin = 10; // minimum X margin between nodes | |
const yMargin = 25; // Y margin between each level | |
// Initial distance between nodes is the minimum distance doubled for each level (aside from bottom level and root level) | |
let nodeDistance = 2**(numLevels - 1) * (nodeSize + xMargin); | |
let startOfNextLevel = 1; // functions as the "outer index" (replacing nested loop from previous version) | |
let x1 = canvasWidth/2; | |
let currentX; | |
let y = nodeSize/2 + yMargin; | |
// For drawing each level a different color: | |
let colorArray = ["#ffffba", "#baffc9", "#bae1ff", "#ffdfba", "#ffb3ba"]; | |
/////////////////////////////////////////////////////////////////////////////////////////////////////////////// | |
function generateAndDrawTree() { | |
console.log("count: " + count); | |
console.log("startOfNextLevel: " + startOfNextLevel); | |
console.log("x: " + currentX); | |
console.log("y: " + y); | |
// For the root node only (this is to visualize the root node as its own independent step) | |
if (count === 1) { | |
node.value = "01"; | |
// DRAWING: | |
currentX = x1; | |
fill( colorArray[0] ); // using log2(startOfNextLevel) would also work as an index here. yay logarithms! :) | |
ellipse(currentX, y, nodeSize, nodeSize); | |
node.x = currentX; | |
node.y = y; | |
// Iterate | |
count++; | |
startOfNextLevel *= 2; | |
console.log( JSON.stringify(tree, null, '\t' ) ); | |
return; | |
} | |
// "outer loop" -- for each new level of nodes: | |
if (count === startOfNextLevel) { | |
fill( colorArray[Math.log2(startOfNextLevel)] ); | |
// For each subsequent level, distance between nodes is halved as we move DOWN a level (where top level is level 1) | |
nodeDistance /= 2; | |
// Update the y coordinate for the next level down | |
y += nodeSize + yMargin; | |
// Update x coordinate of left-most node for the next level down: | |
x1 -= nodeDistance/2; | |
currentX = x1; | |
startOfNextLevel *= 2; // the count/value of first node of each level doubles: 1, 2, 4, 8, 16 ... | |
} | |
// "inner loop" -- generate and draw each node | |
if (count <= totalNodes) { | |
// Conditions only needed to add one node per iteration: | |
if (!node.leftChild) { | |
node.leftChild = {value: count.toString(2)}; | |
nextNodes.push(node.leftChild); | |
count++; | |
// DRAWING: | |
ellipse(currentX, y, nodeSize, nodeSize); | |
node.leftChild.x = currentX; | |
node.leftChild.y = y; | |
currentX = currentX + nodeDistance; | |
} else if (!node.rightChild) { | |
node.rightChild = {value: count.toString(2)}; | |
nextNodes.push(node.rightChild); | |
count++; | |
// DRAWING: | |
ellipse(currentX, y, nodeSize, nodeSize); | |
node.rightChild.x = currentX; | |
node.rightChild.y = y; | |
currentX = currentX + nodeDistance; | |
node = nextNodes.shift() | |
} | |
console.log( JSON.stringify(tree, null, '\t' ) ); | |
} // end looping condition | |
} // end of function |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
//////////////////////////////////////////////////////////////////////////// | |
// Generate a NON-PERFECT binary tree with randomly placed node | |
// of a given depth and total number of nodes | |
//////////////////////////////////////////////////////////////////////////// | |
// NOTE: this version runs all at once, not step-by-step like the other version of the tree generating function | |
function generateRandomTreeWithCoords(numLevels) { | |
// Initialize with the tree itself as the first node | |
let tree = {}; | |
let node = tree; | |
let nextNodes = []; | |
// Initial distance between nodes is the minimum distance doubled for each level (aside from bottom level and root level) | |
let nodeDistance = 2**(numLevels - 1) * (nodeSize + xMargin); // this time: initialize before the loop, half it at end of outer loops | |
let level = 1; | |
let count = 1; | |
let numNodes = 1; | |
let initialX = canvasWidth/2; | |
let y = nodeSize/2 + yMargin; | |
// SET UP ROOT NODE: | |
node.value = numNodes; | |
node.x = initialX; | |
node.y = y; | |
node.level = level; | |
count++; | |
numNodes++; | |
// GO TO NEXT LEVEL: | |
level++; | |
nodeDistance /= 2; // For each subsequent level, distance between nodes is halved as we move DOWN a level (where top level is level 1) | |
y += nodeSize + yMargin; // Update the y coordinate for the next level down | |
console.log( JSON.stringify(tree, null, '\t' ) ); | |
while (node && level <= numLevels) { | |
console.log("level: " + level); | |
console.log("count: " + count); | |
console.log("THIS NODE: " + node.value + " -- " + node.x + ", " + node.y); | |
if ( Math.round(Math.random()) ) { | |
node.leftChild = {value: numNodes}; | |
node.leftChild.x = node.x - nodeDistance/2; | |
node.leftChild.y = y; | |
node.leftChild.level = level; | |
nextNodes.push(node.leftChild); | |
numNodes++; | |
} | |
count++; | |
if ( Math.round(Math.random()) ) { | |
node.rightChild = {value: numNodes}; | |
node.rightChild.x = node.x + nodeDistance/2; | |
node.rightChild.y = y; | |
node.rightChild.level = level; | |
nextNodes.push(node.rightChild); | |
numNodes++; | |
} | |
count++; | |
// GO TO NEXT LEVEL: | |
node = nextNodes.shift(); // iterate through nodes | |
level++; | |
nodeDistance /= 2; // For each subsequent level, distance between nodes is halved as we move DOWN a level (where top level is level 1) | |
y += nodeSize + yMargin; // Update the y coordinate for the next level down | |
console.log( JSON.stringify(tree, null, '\t' ) ); | |
} // end looping condition | |
return tree; | |
} // end of function |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
///////////////////////////////////////////////////////////////////////////////////// | |
// GENERATING TREES OF n LEVELS -- for now, easy mode -- perfect binary trees | |
///////////////////////////////////////////////////////////////////////////////////// | |
////////////////////////////////////////// | |
// SETUP / INITIALIZING VARIBALES | |
///////////////////////////////////////////////////////////////////////////////////// | |
// Iterate step by step with mouse clicks | |
// ***** Use any of the functions defined below as the callback here: ******** | |
document.addEventListener("click", generateTree); | |
// NOTE: To run the code below as a normal loop (instead of stepping through), | |
// just replace the word "if" with "while" in the code, and you're good to go! | |
// PARAMETER -- tree with n levels: | |
let numLevels = 4; | |
// Initialize with the tree itself as the first node | |
let totalNodes = 2**numLevels - 1; // NOTE: assuming the tree will always have a root node | |
let tree = {}; | |
let node = tree; | |
let count = 1; | |
let nextNodes = []; | |
//////////////////////////////////////////////////////////////////////////// | |
function generateTree() { | |
// For iterating step by step -- this way, creating the root node is step 1! | |
if (count === 1) { | |
node.value = "01"; | |
count++; | |
console.log( JSON.stringify(tree, null, '\t' ) ); | |
return; | |
} | |
if (count <= totalNodes) { | |
// Conditions only needed to add one node per iteration: | |
if (!node.leftChild) { | |
node.leftChild = {value: count.toString(2)}; | |
nextNodes.push(node.leftChild); | |
count++; | |
} else if (!node.rightChild) { | |
node.rightChild = {value: count.toString(2)}; | |
nextNodes.push(node.rightChild); | |
count++; | |
node = nextNodes.shift() | |
} | |
console.log( JSON.stringify(tree, null, '\t' ) ); | |
} // end looping condition | |
} // end of function |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// NOTE: this version runs all at once, not step-by-step like the other version of the tree generating function | |
function generateTreeWithCoords(numLevels) { | |
// Initialize with the tree itself as the first node | |
let totalNodes = 2**numLevels - 1; // NOTE: assuming the tree will always have a root node | |
let tree = {}; | |
let node = tree; | |
let count = 1; | |
let nextNodes = []; | |
// Initial distance between nodes is the minimum distance doubled for each level (aside from bottom level and root level) | |
let nodeDistance = 2**(numLevels - 1) * (nodeSize + xMargin); // this time: initialize before the loop, half it at end of outer loops | |
let level = 1; | |
let startOfNextLevel = 1; // functions as the "outer index" (replacing nested loop from previous version) | |
let x1 = canvasWidth/2; | |
let currentX; | |
let y = nodeSize/2 + yMargin; | |
// For the root node only: | |
currentX = x1; | |
node.value = "01"; | |
node.x = currentX; | |
node.y = y; | |
node.level = level; | |
// Iterate | |
count++; | |
startOfNextLevel *= 2; | |
console.log( JSON.stringify(tree, null, '\t' ) ); | |
while (count <= totalNodes) { | |
console.log("count: " + count); | |
console.log("startOfNextLevel: " + startOfNextLevel); | |
console.log("x: " + currentX); | |
console.log("y: " + y); | |
// "outer loop" -- for each new level of nodes: | |
if (count === startOfNextLevel) { | |
// For each subsequent level, distance between nodes is halved as we move DOWN a level (where top level is level 1) | |
nodeDistance /= 2; | |
// Update the y coordinate for the next level down | |
y += nodeSize + yMargin; | |
// Update x coordinate of left-most node for the next level down: | |
x1 -= nodeDistance/2; | |
currentX = x1; | |
level++; | |
startOfNextLevel *= 2; // the count/value of first node of each level doubles: 1, 2, 4, 8, 16 ... | |
} | |
node.leftChild = {value: count.toString(2)}; | |
node.leftChild.x = currentX; | |
node.leftChild.y = y; | |
node.leftChild.level = level; | |
nextNodes.push(node.leftChild); | |
count++; | |
currentX = currentX + nodeDistance; | |
node.rightChild = {value: count.toString(2)}; | |
node.rightChild.x = currentX; | |
node.rightChild.y = y; | |
node.rightChild.level = level; | |
nextNodes.push(node.rightChild); | |
count++; | |
currentX = currentX + nodeDistance; | |
node = nextNodes.shift(); | |
console.log( JSON.stringify(tree, null, '\t' ) ); | |
} // end looping condition | |
return tree; | |
} // end of function |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
///////////////////////////////////////////////////////////////////////////////////// | |
// TRAVERSING ANY BINARY TREE TO FIND ITS HEIGHT / NUM OF LEVELS | |
// First as a step-through function, then as a regular function with output | |
///////////////////////////////////////////////////////////////////////////////////// | |
///////////////////////////////////////////////////////////////////////////////////// | |
// EXAMPLE TREES -- a couple perfect binary trees, and then a couple not perfect ones | |
///////////////////////////////////////////////////////////////////////////////////// | |
let perfectTree1 = { | |
"value": "01", | |
"leftChild": { | |
"value": 10, | |
}, | |
"rightChild": { | |
"value": 11 | |
} | |
}; | |
let perfectTree2 = { | |
"value": "01", | |
"leftChild": { | |
"value": 10, | |
"leftChild": { | |
"value": 100 | |
}, | |
"rightChild": { | |
"value": 101 | |
} | |
}, | |
"rightChild": { | |
"value": 11, | |
"leftChild": { | |
"value": 110 | |
}, | |
"rightChild": { | |
"value": 111 | |
} | |
} | |
}; | |
let bst1 = { | |
"value": "f", | |
"leftChild": { | |
"value": "d", | |
"leftChild": { | |
"value": "b" | |
}, | |
"rightChild": { | |
"value": "e" | |
} | |
}, | |
"rightChild": { | |
"value": "h" | |
} | |
}; | |
let bst2 = { | |
"value": "f", | |
"leftChild": { | |
"value": "d", | |
"leftChild": { | |
"value": "b", | |
"leftChild": { | |
"value": "a" | |
}, | |
"rightChild": { | |
"value": "c" | |
} | |
}, | |
"rightChild": { | |
"value": "e" | |
} | |
}, | |
"rightChild": { | |
"value": "h", | |
"leftChild": { | |
"value": "g" | |
} | |
} | |
}; | |
///////////////////////////////////////////////////////////////////////////////////// | |
// 1. STEP-THROUGH VERSION (for easier visualization / drawing) | |
///////////////////////////////////////////////////////////////////////////////////// | |
/////////////////////////////////////////////// | |
// SETUP / INITIALIZING VARIBALES | |
/////////////////////////////////////////////// | |
// Iterate step by step with mouse clicks | |
document.addEventListener("click", getTreeHeight); | |
// PARAMS: | |
let tree = bst2; // test with an example tree from above | |
// Local vars (if this wasn't a step-through type of loop) | |
let level = 0; // root will be level 0 | |
let currentSet = {level: 0, currentNode: tree}; | |
let unvisitedNodes = []; | |
function getTreeHeight () { | |
// This feels wrong, but my first idea: track the level of each node when added to the stack | |
// so : [ {1, node}, {1, node}, {2, node} ... ] | |
if (currentSet) { | |
console.log("Current Node " + currentSet.currentNode.value + " -- current level: " + currentSet.level); | |
console.log("--------------------------"); | |
console.log(unvisitedNodes); | |
if (currentSet.currentNode.rightChild) { | |
unvisitedNodes.push( {level: currentSet.level + 1, currentNode: currentSet.currentNode.rightChild} ); | |
console.log("Pushed right node " + currentSet.currentNode.rightChild.value); | |
if (currentSet.level + 1 > level) { | |
level = currentSet.level + 1; | |
console.log("Increased num of levels to: " + level); | |
} | |
} | |
if (currentSet.currentNode.leftChild) { | |
unvisitedNodes.push( {level: currentSet.level + 1, currentNode: currentSet.currentNode.leftChild} ); | |
console.log("Pushed left node " + currentSet.currentNode.leftChild.value); | |
if (currentSet.level + 1 > level) { | |
level = currentSet.level + 1; | |
console.log("Increased num of levels to: " + level); | |
} | |
} | |
// console.log("Node " + currentSet.currentNode.value); | |
// console.log(unvisitedNodes); | |
// On next iteration, current node will be the unvisited node | |
// that was most recently added to the stack -- last in, first out! | |
currentSet = unvisitedNodes.pop(); | |
} else { | |
console.log("HEIGHT OF TREE IS: " + level); | |
console.log("Sorry, we're fresh outta nodes!"); | |
} | |
// return level; | |
} // end function | |
///////////////////////////////////////////////////////////////////////////////////// | |
// 2. REGULAR VERSION (loop with output, local vars) | |
///////////////////////////////////////////////////////////////////////////////////// | |
// Test it with an example tree from above -- output will be height of the tree | |
console.log ( getTreeHeight(bst2) ); | |
function getTreeHeight (tree) { | |
// This feels wrong, but my first idea: track the level of each node when added to the stack | |
// so : [ {1, node}, {1, node}, {2, node} ... ] | |
let level = 0; // root will be level 0 | |
let currentSet = {level: 0, currentNode: tree}; | |
let unvisitedNodes = []; | |
while (currentSet) { | |
console.log("Current Node " + currentSet.currentNode.value + " -- current level: " + currentSet.level); | |
console.log("--------------------------"); | |
console.log(unvisitedNodes); | |
if (currentSet.currentNode.rightChild) { | |
unvisitedNodes.push( {level: currentSet.level + 1, currentNode: currentSet.currentNode.rightChild} ); | |
console.log("Pushed right node " + currentSet.currentNode.rightChild.value); | |
if (currentSet.level + 1 > level) { | |
level = currentSet.level + 1; | |
console.log("Increased num of levels to: " + level); | |
} | |
} | |
if (currentSet.currentNode.leftChild) { | |
unvisitedNodes.push( {level: currentSet.level + 1, currentNode: currentSet.currentNode.leftChild} ); | |
console.log("Pushed left node " + currentSet.currentNode.leftChild.value); | |
if (currentSet.level + 1 > level) { | |
level = currentSet.level + 1; | |
console.log("Increased num of levels to: " + level); | |
} | |
} | |
// On next iteration, current node will be the unvisited node | |
// that was most recently added to the stack -- last in, first out! | |
currentSet = unvisitedNodes.pop(); | |
} // end loop | |
console.log("HEIGHT OF TREE IS: " + level); | |
return level; | |
} // end function |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// NOTES: - This depends on calling generateTreeWithCoords(), see other file | |
// - Using p5js for drawing here | |
// - The function traverseAndDrawDepthFirst() runs step-by-step based on mouse clicks | |
function traverseAndDrawTreeDepthFirst () { | |
if (currentNode) { | |
console.log("======================"); | |
console.log("Node " + currentNode.value + " -- level: " + currentNode.level + " -- coords:: " + currentNode.x + ", " + currentNode.y); | |
console.log(unvisitedNodes); | |
// DRAW IT: | |
fill( colorArray[currentNode.level - 1] ); | |
ellipse(currentNode.x, currentNode.y, nodeSize, nodeSize); | |
if (currentNode.rightChild) { | |
unvisitedNodes.push(currentNode.rightChild); | |
console.log("Pushed right node " + currentNode.rightChild.value); | |
} | |
if (currentNode.leftChild) { | |
unvisitedNodes.push(currentNode.leftChild); | |
console.log("Pushed left node " + currentNode.leftChild.value); | |
} | |
// On next iteration, current node will be the unvisited node | |
// that was most recently added to the stack -- last in, first out! | |
currentNode = unvisitedNodes.pop(); | |
} else { | |
console.log("Fresh outa nodes!"); | |
} | |
} | |
////////////////////////////////////////// | |
// SETUP / INITIALIZING VARIBALES | |
///////////////////////////////////////////////////////////////////////////////////// | |
// Setup for drawing: | |
const canvasWidth = 1000, canvasHeight = 900; // canvas dimensions | |
const nodeSize = 50; // given as diameter of each node | |
const xMargin = 10; // minimum X margin between nodes | |
const yMargin = 25; // Y margin between each level | |
// For drawing each level a different color: | |
let colorArray = ["#ffffba", "#baffc9", "#bae1ff", "#ffdfba", "#ffb3ba"] ; | |
//////////////////////////////////////////////////////////////////////////// | |
let myTree = generateTreeWithCoords(5); | |
console.log("=============================="); | |
console.log( JSON.stringify(myTree, null, '\t' ) ); | |
let currentNode = myTree; | |
let unvisitedNodes = []; | |
document.addEventListener("click", traverseAndDrawTreeDepthFirst); | |
console.log("=============================="); | |
//////////////////////////////////////////////////////////////////////////////////////////////////////////// | |
// SETUP FOR P5JS DRAWING: | |
//////////////////////////////////////////////////////////////////////////////////////////////////////////// | |
function setup() { | |
createCanvas(canvasWidth, canvasHeight); | |
// 2 frames per second, easier for testing :) | |
frameRate(2); | |
// DRAW THE TREEEEEE | |
//generateAndDrawTree(); | |
noLoop(); | |
} | |
// Loops for animation, FPS controlled by frameRate() -- default is up to 60 if supported? | |
function draw() { | |
} // end draw() | |
// function mousePressed() { | |
// //iterations++; | |
// redraw(); | |
// } |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
///////////////////////////////////////////////////////////////////////////////////// | |
// EXAMPLE TREES -- for now, easy mode -- perfect binary trees | |
///////////////////////////////////////////////////////////////////////////////////// | |
let tree1 = { | |
"value": "01", | |
"leftChild": { | |
"value": 10, | |
}, | |
"rightChild": { | |
"value": 11 | |
} | |
}; | |
let tree2 = { | |
"value": "01", | |
"leftChild": { | |
"value": 10, | |
"leftChild": { | |
"value": 100 | |
}, | |
"rightChild": { | |
"value": 101 | |
} | |
}, | |
"rightChild": { | |
"value": 11, | |
"leftChild": { | |
"value": 110 | |
}, | |
"rightChild": { | |
"value": 111 | |
} | |
} | |
}; | |
///////////////////////////////////////////////////////////////////////////////////// | |
// SETUP / INITIALIZING VARIBALES | |
///////////////////////////////////////////////////////////////////////////////////// | |
// Iterate step by step with mouse clicks | |
// ***** Use any of the functions defined below as the callback here: ******** | |
document.addEventListener("click", traverseTreeBreadthFirst); | |
// NOTE: To run the code below as a normal loop (instead of stepping through), | |
// just replace the word "if" with "while" in the code, and you're good to go! | |
// Initialize with the tree itself as the first node | |
// ******* Use any of the example trees at the top of this file ******** | |
let currentNode = tree2; | |
let unvisitedNodes = []; | |
//////////////////////////////////////////////////////////////////////////// | |
function traverseTreeBreadthFirst () { | |
console.log("--------------------------"); | |
if (currentNode) { | |
console.log("Node " + currentNode.value); | |
console.log(unvisitedNodes); | |
if (currentNode.leftChild) { | |
unvisitedNodes.push(currentNode.leftChild); | |
console.log("Pushed left node " + currentNode.leftChild.value); | |
} | |
if (currentNode.rightChild) { | |
unvisitedNodes.push(currentNode.rightChild); | |
console.log("Pushed right node " + currentNode.rightChild.value); | |
} | |
console.log("Node " + currentNode.value); | |
console.log(unvisitedNodes); | |
// On next iteration, current node will be the unvisited node | |
// that was FIRST added to the stack -- first in, first out! | |
currentNode = unvisitedNodes.shift(); | |
} else { | |
console.log("Sorry, we're fresh outta nodes!"); | |
} | |
} | |
function traverseTreeDepthFirst () { | |
console.log("--------------------------"); | |
if (currentNode) { | |
console.log("Node " + currentNode.value); | |
console.log(unvisitedNodes); | |
if (currentNode.rightChild) { | |
unvisitedNodes.push(currentNode.rightChild); | |
console.log("Pushed right node " + currentNode.rightChild.value); | |
} | |
if (currentNode.leftChild) { | |
unvisitedNodes.push(currentNode.leftChild); | |
console.log("Pushed left node " + currentNode.leftChild.value); | |
} | |
console.log("Node " + currentNode.value); | |
console.log(unvisitedNodes); | |
// On next iteration, current node will be the unvisited node | |
// that was most recently added to the stack -- last in, first out! | |
currentNode = unvisitedNodes.pop(); | |
} else { | |
console.log("Sorry, we're fresh outta nodes!"); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment