The demo below lets you explore two canonical W[1]-hard problems on small graphs. Build a graph, pick a parameter k, and watch the brute-force search count the candidates it must examine. Then switch to k-Dominating-Set and see that the same exponential blowup appears — because every k-Clique instance can be reduced to a k-Dominating-Set instance in polynomial time, transferring the hardness.
<div class="controls">
<label>Problem:
<select id="problem">
<option value="clique">k-Clique (W[1]-complete)</option>
<option value="domset">k-Dominating Set (W[2]-complete)</option>
</select>
</label>
<label>k = <input id="kval" type="number" min="1" max="5" value="3" style="width:3rem"></label>
<button id="searchBtn" type="button">Find solution</button>
<button id="resetBtn" type="button" class="ghost">Reset</button>
</div>
<div class="graph-wrap">
<svg id="graph" width="340" height="220"></svg>
<div id="legend" class="legend"></div>
</div>
<div id="status" class="status"></div>
<div id="stats" class="stats"></div>
* { box-sizing: border-box; }
body { font-family: system-ui, sans-serif; color: #222; margin: 0; padding: 14px; }
.controls { display: flex; flex-wrap: wrap; gap: .5rem; align-items: center; margin-bottom: .6rem; }
label { font-size: .9rem; }
select, input[type=number] { font-size: .9rem; padding: .25rem .4rem; border: 1px solid #aaa; border-radius: 6px; }
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; }
.graph-wrap { display: flex; gap: 1rem; align-items: flex-start; flex-wrap: wrap; }
#graph { border: 1px solid #dde3e9; border-radius: 10px; background: #f8fafc; flex-shrink: 0; }
.legend { font-size: .82rem; color: #444; line-height: 1.7; }
.status { font-size: 1rem; font-weight: 600; margin: .5rem 0 .2rem; min-height: 1.4em; }
.status.ok { color: #0a7d33; }
.status.bad { color: #c92f3c; }
.stats { font-size: .85rem; color: #555; min-height: 1.2em; }
const fmt = (s, v) => s.replace(/%(\w+)%/g, (_, k) => String(v[k] ?? ''));
// W-hierarchy demo: brute-force k-Clique and k-Dominating-Set on a fixed graph.
// Both problems are W[1]-hard (Clique) / W[2]-hard (DomSet).
// We show that checking a candidate takes O(k^2) but FINDING one requires
// scanning C(n,k) subsets, which grows as n^k — the parameter stays in the exponent.
// Fixed 8-node graph (adjacency list).
const N = 8;
const ADJ_LIST = [
[1,2,3], // 0
[0,2,4], // 1
[0,1,3,5], // 2
[0,2,6], // 3
[1,5,7], // 4
[2,4,6,7], // 5
[3,5,7], // 6
[4,5,6] // 7
];
// Adjacency matrix for O(1) lookup.
const ADJ = Array.from({length:N}, (_,i) => Array.from({length:N}, (_,j) =>
ADJ_LIST[i].includes(j)));
// Node positions (SVG).
const POS = [
[60, 60], [170, 40], [280, 60], [60, 160],
[280, 160], [170, 180], [110, 110], [230, 110]
];
// --- Combinatorics helper ---
function* combinations(arr, k) {
if (k === 0) { yield []; return; }
for (let i = 0; i <= arr.length - k; i++) {
for (const rest of combinations(arr.slice(i+1), k-1)) {
yield [arr[i], ...rest];
}
}
}
// --- Problem checkers ---
function isClique(subset) {
for (let i = 0; i < subset.length; i++)
for (let j = i+1; j < subset.length; j++)
if (!ADJ[subset[i]][subset[j]]) return false;
return true;
}
function isDomSet(subset) {
const covered = new Set(subset);
for (const v of subset) ADJ_LIST[v].forEach(u => covered.add(u));
return covered.size === N;
}
// --- State ---
let highlighted = [];
let problem = 'clique';
let k = 3;
// --- SVG rendering ---
const svg = document.getElementById('graph');
function render() {
svg.innerHTML = '';
// Edges.
for (let i = 0; i < N; i++) for (const j of ADJ_LIST[i]) if (j > i) {
const [x1,y1] = POS[i], [x2,y2] = POS[j];
const inSet = highlighted.includes(i) && highlighted.includes(j);
const line = document.createElementNS('http://www.w3.org/2000/svg','line');
line.setAttribute('x1',x1); line.setAttribute('y1',y1);
line.setAttribute('x2',x2); line.setAttribute('y2',y2);
line.setAttribute('stroke', inSet ? '#e63946' : '#b0b8c4');
line.setAttribute('stroke-width', inSet ? '2.5' : '1.5');
svg.appendChild(line);
}
// Nodes.
for (let i = 0; i < N; i++) {
const [cx,cy] = POS[i];
const inSet = highlighted.includes(i);
const c = document.createElementNS('http://www.w3.org/2000/svg','circle');
c.setAttribute('cx',cx); c.setAttribute('cy',cy); c.setAttribute('r','16');
c.setAttribute('fill', inSet ? '#e63946' : '#d4dbe3');
c.setAttribute('stroke', inSet ? '#c0282f' : '#8b98a8');
c.setAttribute('stroke-width','1.8');
svg.appendChild(c);
const t = document.createElementNS('http://www.w3.org/2000/svg','text');
t.setAttribute('x',cx); t.setAttribute('y',cy+5);
t.setAttribute('text-anchor','middle');
t.setAttribute('font-size','13'); t.setAttribute('font-weight','600');
t.setAttribute('fill', inSet ? '#fff' : '#333');
t.textContent = i;
svg.appendChild(t);
}
}
function setStatus(msg, cls) {
const s = document.getElementById('status');
s.textContent = msg; s.className = 'status ' + (cls||'');
}
function setStats(msg) { document.getElementById('stats').textContent = msg; }
function updateLegend() {
const prob = problem === 'clique'
? '<b>k-Clique</b>: find k vertices all connected to each other.'
: '<b>k-Dominating Set</b>: find k vertices that together neighbor every other vertex.';
document.getElementById('legend').innerHTML =
fmt('%prob%<br>Candidates to check: <b>C(%N%,%k%) = %C%</b><br>Each check: O(k²) = O(%v%) steps.', { prob: prob, N: N, k: k, C: C(N,k), v: k*k });
}
function C(n,r) {
if (r > n) return 0;
let num=1, den=1;
for (let i=0;i<r;i++) { num*=(n-i); den*=(i+1); }
return Math.round(num/den);
}
document.getElementById('problem').addEventListener('change', e => {
problem = e.target.value; highlighted = []; render(); setStatus(''); setStats(''); updateLegend();
});
document.getElementById('kval').addEventListener('input', e => {
k = Math.max(1, Math.min(5, parseInt(e.target.value)||1));
highlighted = []; render(); setStatus(''); setStats(''); updateLegend();
});
document.getElementById('resetBtn').addEventListener('click', () => {
highlighted = []; render(); setStatus(''); setStats('');
});
document.getElementById('searchBtn').addEventListener('click', () => {
const check = problem === 'clique' ? isClique : isDomSet;
const label = problem === 'clique' ? 'clique' : 'dominating set';
let checked = 0, found = null;
const nodes = Array.from({length:N},(_,i)=>i);
for (const combo of combinations(nodes, k)) {
checked++;
if (check(combo)) { found = combo; break; }
}
highlighted = found || [];
render();
const total = C(N,k);
if (found) {
setStatus(fmt('✓ Found a %label% of size %k%: {%found%}} (checked %checked%/%total% subsets)', { label: label, k: k, found: found.join(','), checked: checked, total: total }), 'ok');
console.log('Solution:', found, '| checked', checked, 'of', total, 'subsets');
} else {
setStatus(fmt('✗ No %label% of size %k% exists (checked all %total% subsets)', { label: label, k: k, total: total }), 'bad');
}
setStats(fmt('Subset size k=%k% → n^k ≈ %pow% (brute-force upper bound)', { k: k, pow: Math.pow(N,k) }));
});
// Init.
render();
updateLegend();
setStatus('Choose k and a problem, then click "Find solution".');
<div class="controls">
<label>Problem:
<select id="problem">
<option value="clique">k-Clique (W[1]-complete)</option>
<option value="domset">k-Dominating Set (W[2]-complete)</option>
</select>
</label>
<label>k = <input id="kval" type="number" min="1" max="5" value="3" style="width:3rem"></label>
<button id="searchBtn" type="button">Find solution</button>
<button id="resetBtn" type="button" class="ghost">Reset</button>
</div>
<div class="graph-wrap">
<svg id="graph" width="340" height="220"></svg>
<div id="legend" class="legend"></div>
</div>
<div id="status" class="status"></div>
<div id="stats" class="stats"></div>
* { box-sizing: border-box; }
body { font-family: system-ui, sans-serif; color: #222; margin: 0; padding: 14px; }
.controls { display: flex; flex-wrap: wrap; gap: .5rem; align-items: center; margin-bottom: .6rem; }
label { font-size: .9rem; }
select, input[type=number] { font-size: .9rem; padding: .25rem .4rem; border: 1px solid #aaa; border-radius: 6px; }
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; }
.graph-wrap { display: flex; gap: 1rem; align-items: flex-start; flex-wrap: wrap; }
#graph { border: 1px solid #dde3e9; border-radius: 10px; background: #f8fafc; flex-shrink: 0; }
.legend { font-size: .82rem; color: #444; line-height: 1.7; }
.status { font-size: 1rem; font-weight: 600; margin: .5rem 0 .2rem; min-height: 1.4em; }
.status.ok { color: #0a7d33; }
.status.bad { color: #c92f3c; }
.stats { font-size: .85rem; color: #555; min-height: 1.2em; }
const fmt = (s, v) => s.replace(/%(\w+)%/g, (_, k) => String(v[k] ?? ''));
// W-hierarchy demo: brute-force k-Clique and k-Dominating-Set on a fixed graph.
// Both problems are W[1]-hard (Clique) / W[2]-hard (DomSet).
// We show that checking a candidate takes O(k^2) but FINDING one requires
// scanning C(n,k) subsets, which grows as n^k — the parameter stays in the exponent.
// Fixed 8-node graph (adjacency list).
const N = 8;
const ADJ_LIST = [
[1,2,3], // 0
[0,2,4], // 1
[0,1,3,5], // 2
[0,2,6], // 3
[1,5,7], // 4
[2,4,6,7], // 5
[3,5,7], // 6
[4,5,6] // 7
];
// Adjacency matrix for O(1) lookup.
const ADJ = Array.from({length:N}, (_,i) => Array.from({length:N}, (_,j) =>
ADJ_LIST[i].includes(j)));
// Node positions (SVG).
const POS = [
[60, 60], [170, 40], [280, 60], [60, 160],
[280, 160], [170, 180], [110, 110], [230, 110]
];
// --- Combinatorics helper ---
function* combinations(arr, k) {
if (k === 0) { yield []; return; }
for (let i = 0; i <= arr.length - k; i++) {
for (const rest of combinations(arr.slice(i+1), k-1)) {
yield [arr[i], ...rest];
}
}
}
// --- Problem checkers ---
function isClique(subset) {
for (let i = 0; i < subset.length; i++)
for (let j = i+1; j < subset.length; j++)
if (!ADJ[subset[i]][subset[j]]) return false;
return true;
}
function isDomSet(subset) {
const covered = new Set(subset);
for (const v of subset) ADJ_LIST[v].forEach(u => covered.add(u));
return covered.size === N;
}
// --- State ---
let highlighted = [];
let problem = 'clique';
let k = 3;
// --- SVG rendering ---
const svg = document.getElementById('graph');
function render() {
svg.innerHTML = '';
// Edges.
for (let i = 0; i < N; i++) for (const j of ADJ_LIST[i]) if (j > i) {
const [x1,y1] = POS[i], [x2,y2] = POS[j];
const inSet = highlighted.includes(i) && highlighted.includes(j);
const line = document.createElementNS('http://www.w3.org/2000/svg','line');
line.setAttribute('x1',x1); line.setAttribute('y1',y1);
line.setAttribute('x2',x2); line.setAttribute('y2',y2);
line.setAttribute('stroke', inSet ? '#e63946' : '#b0b8c4');
line.setAttribute('stroke-width', inSet ? '2.5' : '1.5');
svg.appendChild(line);
}
// Nodes.
for (let i = 0; i < N; i++) {
const [cx,cy] = POS[i];
const inSet = highlighted.includes(i);
const c = document.createElementNS('http://www.w3.org/2000/svg','circle');
c.setAttribute('cx',cx); c.setAttribute('cy',cy); c.setAttribute('r','16');
c.setAttribute('fill', inSet ? '#e63946' : '#d4dbe3');
c.setAttribute('stroke', inSet ? '#c0282f' : '#8b98a8');
c.setAttribute('stroke-width','1.8');
svg.appendChild(c);
const t = document.createElementNS('http://www.w3.org/2000/svg','text');
t.setAttribute('x',cx); t.setAttribute('y',cy+5);
t.setAttribute('text-anchor','middle');
t.setAttribute('font-size','13'); t.setAttribute('font-weight','600');
t.setAttribute('fill', inSet ? '#fff' : '#333');
t.textContent = i;
svg.appendChild(t);
}
}
function setStatus(msg, cls) {
const s = document.getElementById('status');
s.textContent = msg; s.className = 'status ' + (cls||'');
}
function setStats(msg) { document.getElementById('stats').textContent = msg; }
function updateLegend() {
const prob = problem === 'clique'
? '<b>k-Clique</b>: find k vertices all connected to each other.'
: '<b>k-Dominating Set</b>: find k vertices that together neighbor every other vertex.';
document.getElementById('legend').innerHTML =
fmt('%prob%<br>Candidates to check: <b>C(%N%,%k%) = %C%</b><br>Each check: O(k²) = O(%v%) steps.', { prob: prob, N: N, k: k, C: C(N,k), v: k*k });
}
function C(n,r) {
if (r > n) return 0;
let num=1, den=1;
for (let i=0;i<r;i++) { num*=(n-i); den*=(i+1); }
return Math.round(num/den);
}
document.getElementById('problem').addEventListener('change', e => {
problem = e.target.value; highlighted = []; render(); setStatus(''); setStats(''); updateLegend();
});
document.getElementById('kval').addEventListener('input', e => {
k = Math.max(1, Math.min(5, parseInt(e.target.value)||1));
highlighted = []; render(); setStatus(''); setStats(''); updateLegend();
});
document.getElementById('resetBtn').addEventListener('click', () => {
highlighted = []; render(); setStatus(''); setStats('');
});
document.getElementById('searchBtn').addEventListener('click', () => {
const check = problem === 'clique' ? isClique : isDomSet;
const label = problem === 'clique' ? 'clique' : 'dominating set';
let checked = 0, found = null;
const nodes = Array.from({length:N},(_,i)=>i);
for (const combo of combinations(nodes, k)) {
checked++;
if (check(combo)) { found = combo; break; }
}
highlighted = found || [];
render();
const total = C(N,k);
if (found) {
setStatus(fmt('✓ Found a %label% of size %k%: {%found%}} (checked %checked%/%total% subsets)', { label: label, k: k, found: found.join(','), checked: checked, total: total }), 'ok');
console.log('Solution:', found, '| checked', checked, 'of', total, 'subsets');
} else {
setStatus(fmt('✗ No %label% of size %k% exists (checked all %total% subsets)', { label: label, k: k, total: total }), 'bad');
}
setStats(fmt('Subset size k=%k% → n^k ≈ %pow% (brute-force upper bound)', { k: k, pow: Math.pow(N,k) }));
});
// Init.
render();
updateLegend();
setStatus('Choose k and a problem, then click "Find solution".');
Notice that checking whether a given set of k vertices is a clique (or a dominating set) takes only O(k2) time — verification is polynomial. But finding such a set means examining C(n, k) candidates, which grows as nk. No matter how clever your search, the parameter k stays in the exponent unless FPT = W[1]. This is exactly what it means to be W[1]-hard.
Comments
Loading comments...