<p class="hint">A <b>kernelization</b> algorithm applies reduction rules in polynomial time to shrink a graph to a <b>kernel</b> — a small equivalent instance bounded by <em>f(k)</em>. Adjust <em>k</em> and watch the rules fire step by step.</p>
<div class="controls">
<label>Parameter <em>k</em> = <span id="kval">4</span></label>
<input type="range" id="kslider" min="3" max="6" value="4" style="width:110px">
<button id="btnStep" type="button">Apply next rule</button>
<button id="btnReset" type="button" class="ghost">Reset</button>
</div>
<div id="log" class="log"></div>
<div class="graph-wrap">
<svg id="graph" width="430" height="270"></svg>
</div>
<div class="legend">
<span class="leg-item"><span class="dot dot-active"></span> Active</span>
<span class="leg-item"><span class="dot dot-cover"></span> Forced into cover</span>
<span class="leg-item"><span class="dot dot-removed"></span> Removed (isolated)</span>
<span class="leg-item"><span class="dot dot-kernel"></span> Kernel vertex</span>
</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: .7rem; flex-wrap: wrap; margin-bottom: .5rem; }
label { font-size: .9rem; font-weight: 600; }
button { font: 600 13px system-ui; padding: .4rem .85rem; border: 1px solid #1d3557;
background: #1d3557; color: #fff; border-radius: 8px; cursor: pointer; }
button.ghost { background: #fff; color: #1d3557; }
button:disabled { opacity: .45; cursor: default; }
.log { font-size: .82rem; color: #333; min-height: 2.8em; padding: .35rem .55rem;
background: #f0f4f8; border-radius: 6px; margin-bottom: .5rem; line-height: 1.5; }
.graph-wrap { border: 1px solid #cdd; border-radius: 8px; background: #f8fbfc;
display: flex; justify-content: center; overflow: hidden; margin-bottom: .4rem; }
svg { display: block; }
.legend { display: flex; gap: .9rem; flex-wrap: wrap; font-size: .8rem; }
.leg-item { display: flex; align-items: center; gap: .3rem; }
.dot { display: inline-block; width: 12px; height: 12px; border-radius: 50%; }
.dot-active { background: #1d3557; }
.dot-cover { background: #e63946; }
.dot-removed { background: #bbb; border: 1px dashed #999; }
.dot-kernel { background: #2a9d8f; }
const fmt = (s, v) => s.replace(/%(\w+)%/g, (_, k) => String(v[k] ?? ''));
// Kernelization of Vertex Cover — parameterized by k
//
// Reduction rules (each fires in O(n+m) — polynomial time):
// R1: Remove isolated vertices (degree 0). They cannot cover any edge.
// R2: If deg(v) > k, force v into the cover, decrement k, remove v and its edges.
// Proof: if we skip v, each of its >k neighbours must be in the cover — but
// that alone exceeds k, so v *must* be included.
// R3 (termination): If active vertices > 2k, answer NO — no VC of size ≤ k exists.
// Proof: every remaining vertex has degree ≤ k (else R2 would fire), so each
// cover vertex covers ≤ k edges. With k cover slots we cover ≤ k² edges.
// If there are >2k active vertices and no isolated ones, there are >k² edges
// — impossible to cover with k vertices. Contradiction.
// Otherwise: ≤2k vertices remain → this IS the kernel.
//
// Graph: 9 vertices.
// H — hub, deg=6 (connects to A,B,C,D,E,F) → triggers R2 when k<6
// I,J — isolated → always removed by R1
// A,B,C,D,E,F — kernel candidates, paired (A-B, C-D, E-F)
// With k=4: R1 removes I,J; R2 forces H (deg 6>4), k→3; 6 vertices left = 2·3 ✓ → KERNEL
const SVG_NS = 'http://www.w3.org/2000/svg';
const ORIG_NODES = [
{ id:0, label:'H', x:215, y:135 },
{ id:1, label:'A', x:80, y:65 },
{ id:2, label:'B', x:80, y:205 },
{ id:3, label:'C', x:170, y:40 },
{ id:4, label:'D', x:260, y:40 },
{ id:5, label:'E', x:350, y:65 },
{ id:6, label:'F', x:350, y:205 },
{ id:7, label:'I', x:25, y:135 },
{ id:8, label:'J', x:405, y:135 },
];
const ORIG_EDGES = [
[0,1],[0,2],[0,3],[0,4],[0,5],[0,6],
[1,2],[3,4],[5,6],
];
let k, nodes, edges, cover;
function getK() { return parseInt(document.getElementById('kslider').value, 10); }
function reset() {
k = getK();
nodes = ORIG_NODES.map(n => ({ ...n, state: 'active' }));
edges = ORIG_EDGES.map(e => ({ a: e[0], b: e[1], alive: true }));
cover = [];
document.getElementById('btnStep').disabled = false;
setLog(fmt('Graph loaded. k = %k%. Click "Apply next rule" to start kernelization.', { k: k }));
draw();
}
function deg(id) {
return edges.filter(e => e.alive && (e.a === id || e.b === id)).length;
}
function setLog(msg) {
document.getElementById('log').textContent = msg;
}
function step() {
// R1
const iso = nodes.find(n => n.state === 'active' && deg(n.id) === 0);
if (iso) {
iso.state = 'removed';
setLog(fmt('Rule R1: %label% is isolated (deg = 0) — removed. It cannot cover any edge.', { label: iso.label }));
draw(); return;
}
// R2
const high = nodes.find(n => n.state === 'active' && deg(n.id) > k);
if (high) {
const d = deg(high.id);
high.state = 'cover';
cover.push(high.label);
edges.forEach(e => { if (e.a === high.id || e.b === high.id) e.alive = false; });
k--;
document.getElementById('kval').textContent = k;
setLog(fmt('Rule R2: deg(%label%) = %d% > k — must be in cover. Edges removed. Remaining k = %k%.', { label: high.label, d: d, k: k }));
draw(); return;
}
// R3
const remaining = nodes.filter(n => n.state === 'active');
if (remaining.length > 2 * k) {
setLog(fmt('Kernel bound exceeded: %length% vertices > 2k = %v%. No vertex cover of original size exists — answer is NO.', { length: remaining.length, v: 2*k }));
document.getElementById('btnStep').disabled = true;
draw(); return;
}
remaining.forEach(n => n.state = 'kernel');
const ae = edges.filter(e => e.alive).length;
setLog(fmt('✔ Kernel found: %length% vertices ≤ 2k = %v%, %ae% edges ≤ k² = %v2%. Forced cover: {%v3%}. Solve the kernel by brute force.', { length: remaining.length, v: 2*k, ae: ae, v2: k*k, v3: cover.length ? cover.join(', ') : '∅' }));
document.getElementById('btnStep').disabled = true;
draw();
}
const STATE_FILL = { active:'#1d3557', cover:'#e63946', removed:'#d8dde2', kernel:'#2a9d8f' };
const STATE_STROKE = { active:'#0d2240', cover:'#a61e28', removed:'#bbb', kernel:'#1f7a6e' };
const TEXT_COLOR = { active:'#fff', cover:'#fff', removed:'#aaa', kernel:'#fff' };
function draw() {
const svg = document.getElementById('graph');
svg.innerHTML = '';
// Edges
edges.forEach(e => {
const a = nodes[e.a], b = nodes[e.b];
const line = document.createElementNS(SVG_NS, 'line');
line.setAttribute('x1', a.x); line.setAttribute('y1', a.y);
line.setAttribute('x2', b.x); line.setAttribute('y2', b.y);
line.setAttribute('stroke', e.alive ? '#7a99b8' : '#dde');
line.setAttribute('stroke-width', e.alive ? '2' : '1');
if (!e.alive) line.setAttribute('stroke-dasharray', '4 3');
svg.appendChild(line);
});
// Nodes
nodes.forEach(n => {
const g = document.createElementNS(SVG_NS, 'g');
const c = document.createElementNS(SVG_NS, 'circle');
c.setAttribute('cx', n.x); c.setAttribute('cy', n.y); c.setAttribute('r', 16);
c.setAttribute('fill', STATE_FILL[n.state]);
c.setAttribute('stroke', STATE_STROKE[n.state]);
c.setAttribute('stroke-width', '2');
if (n.state === 'removed') c.setAttribute('fill-opacity', '0.3');
const t = document.createElementNS(SVG_NS, 'text');
t.setAttribute('x', n.x); t.setAttribute('y', n.y + 5);
t.setAttribute('text-anchor', 'middle');
t.setAttribute('font-size', '12'); t.setAttribute('font-weight', '700');
t.setAttribute('fill', TEXT_COLOR[n.state]);
t.textContent = n.label;
g.appendChild(c); g.appendChild(t);
svg.appendChild(g);
});
// Degree badges for active nodes
nodes.filter(n => n.state === 'active').forEach(n => {
const d = deg(n.id);
const t = document.createElementNS(SVG_NS, 'text');
t.setAttribute('x', n.x + 19); t.setAttribute('y', n.y - 14);
t.setAttribute('font-size', '10'); t.setAttribute('fill', '#555');
t.textContent = 'd=' + d;
svg.appendChild(t);
});
}
document.getElementById('kslider').addEventListener('input', function () {
document.getElementById('kval').textContent = this.value;
reset();
});
document.getElementById('btnStep').addEventListener('click', step);
document.getElementById('btnReset').addEventListener('click', reset);
reset();
<p class="hint">A <b>kernelization</b> algorithm applies reduction rules in polynomial time to shrink a graph to a <b>kernel</b> — a small equivalent instance bounded by <em>f(k)</em>. Adjust <em>k</em> and watch the rules fire step by step.</p>
<div class="controls">
<label>Parameter <em>k</em> = <span id="kval">4</span></label>
<input type="range" id="kslider" min="3" max="6" value="4" style="width:110px">
<button id="btnStep" type="button">Apply next rule</button>
<button id="btnReset" type="button" class="ghost">Reset</button>
</div>
<div id="log" class="log"></div>
<div class="graph-wrap">
<svg id="graph" width="430" height="270"></svg>
</div>
<div class="legend">
<span class="leg-item"><span class="dot dot-active"></span> Active</span>
<span class="leg-item"><span class="dot dot-cover"></span> Forced into cover</span>
<span class="leg-item"><span class="dot dot-removed"></span> Removed (isolated)</span>
<span class="leg-item"><span class="dot dot-kernel"></span> Kernel vertex</span>
</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: .7rem; flex-wrap: wrap; margin-bottom: .5rem; }
label { font-size: .9rem; font-weight: 600; }
button { font: 600 13px system-ui; padding: .4rem .85rem; border: 1px solid #1d3557;
background: #1d3557; color: #fff; border-radius: 8px; cursor: pointer; }
button.ghost { background: #fff; color: #1d3557; }
button:disabled { opacity: .45; cursor: default; }
.log { font-size: .82rem; color: #333; min-height: 2.8em; padding: .35rem .55rem;
background: #f0f4f8; border-radius: 6px; margin-bottom: .5rem; line-height: 1.5; }
.graph-wrap { border: 1px solid #cdd; border-radius: 8px; background: #f8fbfc;
display: flex; justify-content: center; overflow: hidden; margin-bottom: .4rem; }
svg { display: block; }
.legend { display: flex; gap: .9rem; flex-wrap: wrap; font-size: .8rem; }
.leg-item { display: flex; align-items: center; gap: .3rem; }
.dot { display: inline-block; width: 12px; height: 12px; border-radius: 50%; }
.dot-active { background: #1d3557; }
.dot-cover { background: #e63946; }
.dot-removed { background: #bbb; border: 1px dashed #999; }
.dot-kernel { background: #2a9d8f; }
const fmt = (s, v) => s.replace(/%(\w+)%/g, (_, k) => String(v[k] ?? ''));
// Kernelization of Vertex Cover — parameterized by k
//
// Reduction rules (each fires in O(n+m) — polynomial time):
// R1: Remove isolated vertices (degree 0). They cannot cover any edge.
// R2: If deg(v) > k, force v into the cover, decrement k, remove v and its edges.
// Proof: if we skip v, each of its >k neighbours must be in the cover — but
// that alone exceeds k, so v *must* be included.
// R3 (termination): If active vertices > 2k, answer NO — no VC of size ≤ k exists.
// Proof: every remaining vertex has degree ≤ k (else R2 would fire), so each
// cover vertex covers ≤ k edges. With k cover slots we cover ≤ k² edges.
// If there are >2k active vertices and no isolated ones, there are >k² edges
// — impossible to cover with k vertices. Contradiction.
// Otherwise: ≤2k vertices remain → this IS the kernel.
//
// Graph: 9 vertices.
// H — hub, deg=6 (connects to A,B,C,D,E,F) → triggers R2 when k<6
// I,J — isolated → always removed by R1
// A,B,C,D,E,F — kernel candidates, paired (A-B, C-D, E-F)
// With k=4: R1 removes I,J; R2 forces H (deg 6>4), k→3; 6 vertices left = 2·3 ✓ → KERNEL
const SVG_NS = 'http://www.w3.org/2000/svg';
const ORIG_NODES = [
{ id:0, label:'H', x:215, y:135 },
{ id:1, label:'A', x:80, y:65 },
{ id:2, label:'B', x:80, y:205 },
{ id:3, label:'C', x:170, y:40 },
{ id:4, label:'D', x:260, y:40 },
{ id:5, label:'E', x:350, y:65 },
{ id:6, label:'F', x:350, y:205 },
{ id:7, label:'I', x:25, y:135 },
{ id:8, label:'J', x:405, y:135 },
];
const ORIG_EDGES = [
[0,1],[0,2],[0,3],[0,4],[0,5],[0,6],
[1,2],[3,4],[5,6],
];
let k, nodes, edges, cover;
function getK() { return parseInt(document.getElementById('kslider').value, 10); }
function reset() {
k = getK();
nodes = ORIG_NODES.map(n => ({ ...n, state: 'active' }));
edges = ORIG_EDGES.map(e => ({ a: e[0], b: e[1], alive: true }));
cover = [];
document.getElementById('btnStep').disabled = false;
setLog(fmt('Graph loaded. k = %k%. Click "Apply next rule" to start kernelization.', { k: k }));
draw();
}
function deg(id) {
return edges.filter(e => e.alive && (e.a === id || e.b === id)).length;
}
function setLog(msg) {
document.getElementById('log').textContent = msg;
}
function step() {
// R1
const iso = nodes.find(n => n.state === 'active' && deg(n.id) === 0);
if (iso) {
iso.state = 'removed';
setLog(fmt('Rule R1: %label% is isolated (deg = 0) — removed. It cannot cover any edge.', { label: iso.label }));
draw(); return;
}
// R2
const high = nodes.find(n => n.state === 'active' && deg(n.id) > k);
if (high) {
const d = deg(high.id);
high.state = 'cover';
cover.push(high.label);
edges.forEach(e => { if (e.a === high.id || e.b === high.id) e.alive = false; });
k--;
document.getElementById('kval').textContent = k;
setLog(fmt('Rule R2: deg(%label%) = %d% > k — must be in cover. Edges removed. Remaining k = %k%.', { label: high.label, d: d, k: k }));
draw(); return;
}
// R3
const remaining = nodes.filter(n => n.state === 'active');
if (remaining.length > 2 * k) {
setLog(fmt('Kernel bound exceeded: %length% vertices > 2k = %v%. No vertex cover of original size exists — answer is NO.', { length: remaining.length, v: 2*k }));
document.getElementById('btnStep').disabled = true;
draw(); return;
}
remaining.forEach(n => n.state = 'kernel');
const ae = edges.filter(e => e.alive).length;
setLog(fmt('✔ Kernel found: %length% vertices ≤ 2k = %v%, %ae% edges ≤ k² = %v2%. Forced cover: {%v3%}. Solve the kernel by brute force.', { length: remaining.length, v: 2*k, ae: ae, v2: k*k, v3: cover.length ? cover.join(', ') : '∅' }));
document.getElementById('btnStep').disabled = true;
draw();
}
const STATE_FILL = { active:'#1d3557', cover:'#e63946', removed:'#d8dde2', kernel:'#2a9d8f' };
const STATE_STROKE = { active:'#0d2240', cover:'#a61e28', removed:'#bbb', kernel:'#1f7a6e' };
const TEXT_COLOR = { active:'#fff', cover:'#fff', removed:'#aaa', kernel:'#fff' };
function draw() {
const svg = document.getElementById('graph');
svg.innerHTML = '';
// Edges
edges.forEach(e => {
const a = nodes[e.a], b = nodes[e.b];
const line = document.createElementNS(SVG_NS, 'line');
line.setAttribute('x1', a.x); line.setAttribute('y1', a.y);
line.setAttribute('x2', b.x); line.setAttribute('y2', b.y);
line.setAttribute('stroke', e.alive ? '#7a99b8' : '#dde');
line.setAttribute('stroke-width', e.alive ? '2' : '1');
if (!e.alive) line.setAttribute('stroke-dasharray', '4 3');
svg.appendChild(line);
});
// Nodes
nodes.forEach(n => {
const g = document.createElementNS(SVG_NS, 'g');
const c = document.createElementNS(SVG_NS, 'circle');
c.setAttribute('cx', n.x); c.setAttribute('cy', n.y); c.setAttribute('r', 16);
c.setAttribute('fill', STATE_FILL[n.state]);
c.setAttribute('stroke', STATE_STROKE[n.state]);
c.setAttribute('stroke-width', '2');
if (n.state === 'removed') c.setAttribute('fill-opacity', '0.3');
const t = document.createElementNS(SVG_NS, 'text');
t.setAttribute('x', n.x); t.setAttribute('y', n.y + 5);
t.setAttribute('text-anchor', 'middle');
t.setAttribute('font-size', '12'); t.setAttribute('font-weight', '700');
t.setAttribute('fill', TEXT_COLOR[n.state]);
t.textContent = n.label;
g.appendChild(c); g.appendChild(t);
svg.appendChild(g);
});
// Degree badges for active nodes
nodes.filter(n => n.state === 'active').forEach(n => {
const d = deg(n.id);
const t = document.createElementNS(SVG_NS, 'text');
t.setAttribute('x', n.x + 19); t.setAttribute('y', n.y - 14);
t.setAttribute('font-size', '10'); t.setAttribute('fill', '#555');
t.textContent = 'd=' + d;
svg.appendChild(t);
});
}
document.getElementById('kslider').addEventListener('input', function () {
document.getElementById('kval').textContent = this.value;
reset();
});
document.getElementById('btnStep').addEventListener('click', step);
document.getElementById('btnReset').addEventListener('click', reset);
reset();
Comments
Loading comments...