<p class="hint">A treap stores each node's <b>key</b> (BST order, shown in the box) and a random <b>priority</b> (smaller number = higher priority, shown in blue below the key). Use <b>Insert</b> to add a key, <b>Split</b> to divide at a threshold, and <b>Merge</b> to reunite.</p>
<div class="controls">
<label>Key: <input id="key-in" type="number" min="1" max="99" value="50" style="width:52px"></label>
<button id="btn-insert">Insert</button>
<span class="sep">|</span>
<label>Split ≤ <input id="split-in" type="number" min="1" max="99" value="40" style="width:52px"></label>
<button id="btn-split">Split</button>
<button id="btn-merge" disabled>Merge back</button>
<button id="btn-reset" class="ghost">Reset</button>
</div>
<div id="msg" class="msg"></div>
<div id="canvas-wrap"><canvas id="cv" width="680" height="280"></canvas></div>
* { box-sizing: border-box; }
body { font-family: system-ui, sans-serif; color: #222; margin: 0; }
.hint { font-size: .88rem; color: #444; margin: 0 0 .6rem; line-height: 1.45; }
.controls { display: flex; align-items: center; gap: .45rem; flex-wrap: wrap; margin-bottom: .5rem; }
label { font-size: .9rem; }
input[type=number] { font: inherit; padding: .2rem .3rem; border: 1px solid #adb1b8; border-radius: 6px; }
button { font: 600 13px system-ui; padding: .38rem .75rem; border: 1px solid #1d3557;
background: #1d3557; color: #fff; border-radius: 7px; cursor: pointer; }
button.ghost { background: #fff; color: #1d3557; }
button:disabled { opacity: .4; cursor: default; }
.sep { color: #aaa; }
.msg { font-size: .93rem; font-weight: 600; min-height: 1.3em; margin-bottom: .3rem; }
.msg.ok { color: #0a7d33; }
.msg.bad { color: #c92f3c; }
#canvas-wrap { overflow-x: auto; }
canvas { display: block; background: #f7f9fb; border-radius: 10px; border: 1px solid #dde3ea; max-width: 100%; }
const fmt = (s, v) => s.replace(/%(\w+)%/g, (_, k) => String(v[k] ?? ''));
// Treap: each node has a key (BST invariant) and a random priority (max-heap invariant).
// Core operations: split(t, x) -> [left, right] where left.key <= x < right.key
// merge(l, r) -> treap where all l.key < r.key
// Both run in O(log n) expected time.
let uid = 0;
function node(key) {
return { key, prio: Math.floor(Math.random() * 1000), left: null, right: null, id: ++uid };
}
// split: returns [left, right] where left has keys <= x, right has keys > x
function split(t, x) {
if (!t) return [null, null];
if (t.key <= x) {
const [rl, rr] = split(t.right, x);
return [{ ...t, right: rl }, rr];
} else {
const [ll, lr] = split(t.left, x);
return [ll, { ...t, left: lr }];
}
}
// merge: all keys in l must be < all keys in r
function merge(l, r) {
if (!l) return r;
if (!r) return l;
if (l.prio >= r.prio) {
return { ...l, right: merge(l.right, r) };
} else {
return { ...r, left: merge(l, r.left) };
}
}
function insert(t, key) {
const [l, r] = split(t, key - 1);
return merge(merge(l, node(key)), r);
}
// ── State ─────────────────────────────────────────────────────────────────
let root = null;
let splitLeft = null, splitRight = null;
let isSplit = false;
// Seed with a few initial nodes
[20, 50, 10, 35, 65, 80].forEach(k => { root = insert(root, k); });
// ── Drawing ───────────────────────────────────────────────────────────────
const cv = document.getElementById('cv');
const ctx = cv.getContext('2d');
const W = cv.width, H = cv.height;
const NODE_R = 22;
function treeSize(t) { return t ? 1 + treeSize(t.left) + treeSize(t.right) : 0; }
// Assign x positions via in-order traversal
function assignX(t, counter) {
if (!t) return counter;
counter = assignX(t.left, counter);
t._x = counter++;
counter = assignX(t.right, counter);
return counter;
}
// Assign y by depth
function assignY(t, depth) {
if (!t) return;
t._depth = depth;
assignY(t.left, depth + 1);
assignY(t.right, depth + 1);
}
function drawTree(t, parent, xOff, color) {
if (!t) return;
const x = xOff + t._x * 52 + 26;
const y = 24 + t._depth * 60;
t._px = x; t._py = y;
if (parent) {
ctx.beginPath();
ctx.moveTo(parent._px, parent._py + NODE_R);
ctx.lineTo(x, y - NODE_R);
ctx.strokeStyle = '#aab';
ctx.lineWidth = 1.5;
ctx.stroke();
}
// Node circle
ctx.beginPath();
ctx.arc(x, y, NODE_R, 0, Math.PI * 2);
ctx.fillStyle = color;
ctx.fill();
ctx.strokeStyle = '#1d3557';
ctx.lineWidth = 1.5;
ctx.stroke();
// Key
ctx.fillStyle = '#1d3557';
ctx.font = 'bold 13px ui-monospace, monospace';
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.fillText(t.key, x, y - 3);
// Priority (smaller = higher)
ctx.fillStyle = '#2a6db5';
ctx.font = '10px ui-monospace, monospace';
ctx.fillText('p' + t.prio, x, y + 10);
drawTree(t.left, t, xOff, color);
drawTree(t.right, t, xOff, color);
}
function render() {
ctx.clearRect(0, 0, W, H);
if (!isSplit) {
if (!root) {
ctx.fillStyle = '#999';
ctx.font = '14px system-ui';
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.fillText('Tree is empty. Insert some keys!', W / 2, H / 2);
return;
}
assignX(root, 0);
assignY(root, 0);
drawTree(root, null, 10, '#dbeafe');
} else {
// Draw left treap (green tint) and right treap (red tint) side by side
const lSize = treeSize(splitLeft);
const rSize = treeSize(splitRight);
const lWidth = lSize * 52 + 20;
if (splitLeft) {
assignX(splitLeft, 0);
assignY(splitLeft, 0);
drawTree(splitLeft, null, 10, '#d1fae5');
// Label
ctx.fillStyle = '#065f46';
ctx.font = 'bold 12px system-ui';
ctx.textAlign = 'left';
ctx.textBaseline = 'top';
ctx.fillText('Left (keys ≤ split)', 12, 4);
}
if (splitRight) {
const xOff = splitLeft ? lWidth + 10 : 10;
assignX(splitRight, 0);
assignY(splitRight, 0);
drawTree(splitRight, null, xOff, '#fee2e2');
ctx.fillStyle = '#7f1d1d';
ctx.font = 'bold 12px system-ui';
ctx.textAlign = 'left';
ctx.textBaseline = 'top';
ctx.fillText('Right (keys > split)', xOff + 2, 4);
}
if (!splitLeft && !splitRight) {
ctx.fillStyle = '#999';
ctx.font = '14px system-ui';
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.fillText('Both halves are empty.', W / 2, H / 2);
}
}
}
// ── Validation ────────────────────────────────────────────────────────────
function validateBST(t, min, max) {
if (!t) return true;
if (t.key < min || t.key > max) return false;
return validateBST(t.left, min, t.key - 1) && validateBST(t.right, t.key + 1, max);
}
function validateHeap(t) {
if (!t) return true;
if (t.left && t.left.prio > t.prio) return false;
if (t.right && t.right.prio > t.prio) return false;
return validateHeap(t.left) && validateHeap(t.right);
}
function msg(text, cls) {
const el = document.getElementById('msg');
el.textContent = text;
el.className = 'msg ' + (cls || '');
}
// ── Button handlers ───────────────────────────────────────────────────────────
document.getElementById('btn-insert').addEventListener('click', () => {
if (isSplit) { msg('Merge first before inserting.', 'bad'); return; }
const k = parseInt(document.getElementById('key-in').value, 10);
if (isNaN(k) || k < 1 || k > 99) { msg('Key must be 1–99.', 'bad'); return; }
root = insert(root, k);
const bst = validateBST(root, -Infinity, Infinity);
const heap = validateHeap(root);
msg(fmt('Inserted %k%. BST: %v% Heap: %v2%', { k: k, v: bst ? '✓' : '✗', v2: heap ? '✓' : '✗' }), bst && heap ? 'ok' : 'bad');
render();
});
document.getElementById('btn-split').addEventListener('click', () => {
if (isSplit) { msg('Already split — merge first.', 'bad'); return; }
const x = parseInt(document.getElementById('split-in').value, 10);
if (isNaN(x) || x < 1 || x > 99) { msg('Split threshold must be 1–99.', 'bad'); return; }
[splitLeft, splitRight] = split(root, x);
root = null;
isSplit = true;
document.getElementById('btn-merge').disabled = false;
const lKeys = [];
function collect(t) { if (!t) return; collect(t.left); lKeys.push(t.key); collect(t.right); }
const rKeys = [];
function collectR(t) { if (!t) return; collectR(t.left); rKeys.push(t.key); collectR(t.right); }
collect(splitLeft);
collectR(splitRight);
msg(fmt('Split at %x%. Left: [%v%] Right: [%v2%]', { x: x, v: lKeys.join(', ') || '∅', v2: rKeys.join(', ') || '∅' }), 'ok');
render();
});
document.getElementById('btn-merge').addEventListener('click', () => {
if (!isSplit) return;
root = merge(splitLeft, splitRight);
splitLeft = splitRight = null;
isSplit = false;
document.getElementById('btn-merge').disabled = true;
const bst = validateBST(root, -Infinity, Infinity);
const heap = validateHeap(root);
msg(fmt('Merged! BST: %v% Heap: %v2%', { v: bst ? '✓' : '✗', v2: heap ? '✓' : '✗' }), bst && heap ? 'ok' : 'bad');
render();
});
document.getElementById('btn-reset').addEventListener('click', () => {
root = null; splitLeft = null; splitRight = null; isSplit = false;
document.getElementById('btn-merge').disabled = true;
[20, 50, 10, 35, 65, 80].forEach(k => { root = insert(root, k); });
msg('Reset to default tree.', 'ok');
render();
});
render();
msg('Tree seeded with 6 random-priority nodes.', 'ok');
// Log validation result to console for external verification
console.log('Initial BST valid:', validateBST(root, -Infinity, Infinity));
console.log('Initial Heap valid:', validateHeap(root));
<p class="hint">A treap stores each node's <b>key</b> (BST order, shown in the box) and a random <b>priority</b> (smaller number = higher priority, shown in blue below the key). Use <b>Insert</b> to add a key, <b>Split</b> to divide at a threshold, and <b>Merge</b> to reunite.</p>
<div class="controls">
<label>Key: <input id="key-in" type="number" min="1" max="99" value="50" style="width:52px"></label>
<button id="btn-insert">Insert</button>
<span class="sep">|</span>
<label>Split ≤ <input id="split-in" type="number" min="1" max="99" value="40" style="width:52px"></label>
<button id="btn-split">Split</button>
<button id="btn-merge" disabled>Merge back</button>
<button id="btn-reset" class="ghost">Reset</button>
</div>
<div id="msg" class="msg"></div>
<div id="canvas-wrap"><canvas id="cv" width="680" height="280"></canvas></div>
* { box-sizing: border-box; }
body { font-family: system-ui, sans-serif; color: #222; margin: 0; }
.hint { font-size: .88rem; color: #444; margin: 0 0 .6rem; line-height: 1.45; }
.controls { display: flex; align-items: center; gap: .45rem; flex-wrap: wrap; margin-bottom: .5rem; }
label { font-size: .9rem; }
input[type=number] { font: inherit; padding: .2rem .3rem; border: 1px solid #adb1b8; border-radius: 6px; }
button { font: 600 13px system-ui; padding: .38rem .75rem; border: 1px solid #1d3557;
background: #1d3557; color: #fff; border-radius: 7px; cursor: pointer; }
button.ghost { background: #fff; color: #1d3557; }
button:disabled { opacity: .4; cursor: default; }
.sep { color: #aaa; }
.msg { font-size: .93rem; font-weight: 600; min-height: 1.3em; margin-bottom: .3rem; }
.msg.ok { color: #0a7d33; }
.msg.bad { color: #c92f3c; }
#canvas-wrap { overflow-x: auto; }
canvas { display: block; background: #f7f9fb; border-radius: 10px; border: 1px solid #dde3ea; max-width: 100%; }
const fmt = (s, v) => s.replace(/%(\w+)%/g, (_, k) => String(v[k] ?? ''));
// Treap: each node has a key (BST invariant) and a random priority (max-heap invariant).
// Core operations: split(t, x) -> [left, right] where left.key <= x < right.key
// merge(l, r) -> treap where all l.key < r.key
// Both run in O(log n) expected time.
let uid = 0;
function node(key) {
return { key, prio: Math.floor(Math.random() * 1000), left: null, right: null, id: ++uid };
}
// split: returns [left, right] where left has keys <= x, right has keys > x
function split(t, x) {
if (!t) return [null, null];
if (t.key <= x) {
const [rl, rr] = split(t.right, x);
return [{ ...t, right: rl }, rr];
} else {
const [ll, lr] = split(t.left, x);
return [ll, { ...t, left: lr }];
}
}
// merge: all keys in l must be < all keys in r
function merge(l, r) {
if (!l) return r;
if (!r) return l;
if (l.prio >= r.prio) {
return { ...l, right: merge(l.right, r) };
} else {
return { ...r, left: merge(l, r.left) };
}
}
function insert(t, key) {
const [l, r] = split(t, key - 1);
return merge(merge(l, node(key)), r);
}
// ── State ─────────────────────────────────────────────────────────────────
let root = null;
let splitLeft = null, splitRight = null;
let isSplit = false;
// Seed with a few initial nodes
[20, 50, 10, 35, 65, 80].forEach(k => { root = insert(root, k); });
// ── Drawing ───────────────────────────────────────────────────────────────
const cv = document.getElementById('cv');
const ctx = cv.getContext('2d');
const W = cv.width, H = cv.height;
const NODE_R = 22;
function treeSize(t) { return t ? 1 + treeSize(t.left) + treeSize(t.right) : 0; }
// Assign x positions via in-order traversal
function assignX(t, counter) {
if (!t) return counter;
counter = assignX(t.left, counter);
t._x = counter++;
counter = assignX(t.right, counter);
return counter;
}
// Assign y by depth
function assignY(t, depth) {
if (!t) return;
t._depth = depth;
assignY(t.left, depth + 1);
assignY(t.right, depth + 1);
}
function drawTree(t, parent, xOff, color) {
if (!t) return;
const x = xOff + t._x * 52 + 26;
const y = 24 + t._depth * 60;
t._px = x; t._py = y;
if (parent) {
ctx.beginPath();
ctx.moveTo(parent._px, parent._py + NODE_R);
ctx.lineTo(x, y - NODE_R);
ctx.strokeStyle = '#aab';
ctx.lineWidth = 1.5;
ctx.stroke();
}
// Node circle
ctx.beginPath();
ctx.arc(x, y, NODE_R, 0, Math.PI * 2);
ctx.fillStyle = color;
ctx.fill();
ctx.strokeStyle = '#1d3557';
ctx.lineWidth = 1.5;
ctx.stroke();
// Key
ctx.fillStyle = '#1d3557';
ctx.font = 'bold 13px ui-monospace, monospace';
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.fillText(t.key, x, y - 3);
// Priority (smaller = higher)
ctx.fillStyle = '#2a6db5';
ctx.font = '10px ui-monospace, monospace';
ctx.fillText('p' + t.prio, x, y + 10);
drawTree(t.left, t, xOff, color);
drawTree(t.right, t, xOff, color);
}
function render() {
ctx.clearRect(0, 0, W, H);
if (!isSplit) {
if (!root) {
ctx.fillStyle = '#999';
ctx.font = '14px system-ui';
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.fillText('Tree is empty. Insert some keys!', W / 2, H / 2);
return;
}
assignX(root, 0);
assignY(root, 0);
drawTree(root, null, 10, '#dbeafe');
} else {
// Draw left treap (green tint) and right treap (red tint) side by side
const lSize = treeSize(splitLeft);
const rSize = treeSize(splitRight);
const lWidth = lSize * 52 + 20;
if (splitLeft) {
assignX(splitLeft, 0);
assignY(splitLeft, 0);
drawTree(splitLeft, null, 10, '#d1fae5');
// Label
ctx.fillStyle = '#065f46';
ctx.font = 'bold 12px system-ui';
ctx.textAlign = 'left';
ctx.textBaseline = 'top';
ctx.fillText('Left (keys ≤ split)', 12, 4);
}
if (splitRight) {
const xOff = splitLeft ? lWidth + 10 : 10;
assignX(splitRight, 0);
assignY(splitRight, 0);
drawTree(splitRight, null, xOff, '#fee2e2');
ctx.fillStyle = '#7f1d1d';
ctx.font = 'bold 12px system-ui';
ctx.textAlign = 'left';
ctx.textBaseline = 'top';
ctx.fillText('Right (keys > split)', xOff + 2, 4);
}
if (!splitLeft && !splitRight) {
ctx.fillStyle = '#999';
ctx.font = '14px system-ui';
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.fillText('Both halves are empty.', W / 2, H / 2);
}
}
}
// ── Validation ────────────────────────────────────────────────────────────
function validateBST(t, min, max) {
if (!t) return true;
if (t.key < min || t.key > max) return false;
return validateBST(t.left, min, t.key - 1) && validateBST(t.right, t.key + 1, max);
}
function validateHeap(t) {
if (!t) return true;
if (t.left && t.left.prio > t.prio) return false;
if (t.right && t.right.prio > t.prio) return false;
return validateHeap(t.left) && validateHeap(t.right);
}
function msg(text, cls) {
const el = document.getElementById('msg');
el.textContent = text;
el.className = 'msg ' + (cls || '');
}
// ── Button handlers ───────────────────────────────────────────────────────────
document.getElementById('btn-insert').addEventListener('click', () => {
if (isSplit) { msg('Merge first before inserting.', 'bad'); return; }
const k = parseInt(document.getElementById('key-in').value, 10);
if (isNaN(k) || k < 1 || k > 99) { msg('Key must be 1–99.', 'bad'); return; }
root = insert(root, k);
const bst = validateBST(root, -Infinity, Infinity);
const heap = validateHeap(root);
msg(fmt('Inserted %k%. BST: %v% Heap: %v2%', { k: k, v: bst ? '✓' : '✗', v2: heap ? '✓' : '✗' }), bst && heap ? 'ok' : 'bad');
render();
});
document.getElementById('btn-split').addEventListener('click', () => {
if (isSplit) { msg('Already split — merge first.', 'bad'); return; }
const x = parseInt(document.getElementById('split-in').value, 10);
if (isNaN(x) || x < 1 || x > 99) { msg('Split threshold must be 1–99.', 'bad'); return; }
[splitLeft, splitRight] = split(root, x);
root = null;
isSplit = true;
document.getElementById('btn-merge').disabled = false;
const lKeys = [];
function collect(t) { if (!t) return; collect(t.left); lKeys.push(t.key); collect(t.right); }
const rKeys = [];
function collectR(t) { if (!t) return; collectR(t.left); rKeys.push(t.key); collectR(t.right); }
collect(splitLeft);
collectR(splitRight);
msg(fmt('Split at %x%. Left: [%v%] Right: [%v2%]', { x: x, v: lKeys.join(', ') || '∅', v2: rKeys.join(', ') || '∅' }), 'ok');
render();
});
document.getElementById('btn-merge').addEventListener('click', () => {
if (!isSplit) return;
root = merge(splitLeft, splitRight);
splitLeft = splitRight = null;
isSplit = false;
document.getElementById('btn-merge').disabled = true;
const bst = validateBST(root, -Infinity, Infinity);
const heap = validateHeap(root);
msg(fmt('Merged! BST: %v% Heap: %v2%', { v: bst ? '✓' : '✗', v2: heap ? '✓' : '✗' }), bst && heap ? 'ok' : 'bad');
render();
});
document.getElementById('btn-reset').addEventListener('click', () => {
root = null; splitLeft = null; splitRight = null; isSplit = false;
document.getElementById('btn-merge').disabled = true;
[20, 50, 10, 35, 65, 80].forEach(k => { root = insert(root, k); });
msg('Reset to default tree.', 'ok');
render();
});
render();
msg('Tree seeded with 6 random-priority nodes.', 'ok');
// Log validation result to console for external verification
console.log('Initial BST valid:', validateBST(root, -Infinity, Infinity));
console.log('Initial Heap valid:', validateHeap(root));
Notice how the tree reorganizes after every operation yet always satisfies both invariants. Checking that a tree is a valid treap takes O(n) — just walk every node and verify the BST and heap conditions. Building an optimal balanced BST from scratch without randomness is harder (see binary search trees and dynamic shortest paths for related ideas about optimal structure).
Comments
Loading comments...