文字说明
采用GPT生成的常用搜索算法的网页动画演示效果,帮助理解常用的一些搜索算法的实现思路
核心代码
DFS搜索算法
const ROWS = 20;
const COLS = 20;
let grid = [];
let startCell = null;
let endCell = null;
let isRunning = false;
function createGrid() {
const gridContainer = document.getElementById('grid-container');
for (let row = 0; row < ROWS; row++) {
const rowArray = [];
for (let col = 0; col < COLS; col++) {
const cell = document.createElement('div');
cell.classList.add('cell');
cell.dataset.row = row;
cell.dataset.col = col;
cell.addEventListener('click', handleCellClick);
gridContainer.appendChild(cell);
rowArray.push(cell);
}
grid.push(rowArray);
}
}
function handleCellClick(event) {
const cell = event.target;
if (!startCell && !cell.classList.contains('end')) {
cell.classList.add('start');
startCell = cell;
} else if (!endCell && cell !== startCell) {
cell.classList.add('end');
endCell = cell;
}
}
async function dfs(start, end) {
if (isRunning) return;
isRunning = true;
resetGrid();
const stack = [start];
const visited = new Set();
const parentMap = {};
let found = false;
while (stack.length > 0) {
const current = stack.pop();
const [row, col] = current;
if (visited.has(`${row},${col}`)) continue;
visited.add(`${row},${col}`);
grid[row][col].classList.add('visited');
if (grid[row][col] === endCell) {
found = true;
break;
}
const neighbors = getNeighbors(row, col).reverse(); // Reverse to prioritize up, left, down, right
for (const neighbor of neighbors) {
const [nRow, nCol] = neighbor;
if (!visited.has(`${nRow},${nCol}`)) {
stack.push([nRow, nCol]);
parentMap[`${nRow},${nCol}`] = [row, col];
}
}
// Animation delay
await sleep(100); // Wait for 1 second between steps
}
if (found) {
reconstructPath(parentMap, end);
}
isRunning = false;
enableStartButton();
}
function getNeighbors(row, col) {
const neighbors = [];
const directions = [
[0, 1], // right
[1, 0], // down
[0, -1], // left
[-1, 0] // up
];
for (const [dr, dc] of directions) {
const newRow = row + dr;
const newCol = col + dc;
if (newRow >= 0 && newRow < ROWS && newCol >= 0 && newCol < COLS) {
neighbors.push([newRow, newCol]);
}
}
return neighbors;
}
function reconstructPath(parentMap, end) {
let path = [];
let current = end;
while (current) {
path.unshift(current);
current = parentMap[current.join(',')] || null;
}
for (const [row, col] of path) {
if (grid[row][col] !== startCell && grid[row][col] !== endCell) {
grid[row][col].classList.add('path');
}
}
}
document.getElementById('start-btn').addEventListener('click', async () => {
disableStartButton();
if (startCell && endCell) {
await dfs([parseInt(startCell.dataset.row), parseInt(startCell.dataset.col)], [parseInt(endCell.dataset.row), parseInt(endCell.dataset.col)]);
} else {
alert('Please select both start and end cells.');
enableStartButton();
}
});
createGrid();
// Helper function to create a delay
function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
function disableStartButton() {
const startBtn = document.getElementById('start-btn');
startBtn.disabled = true;
startBtn.textContent = 'Running...';
}
function enableStartButton() {
const startBtn = document.getElementById('start-btn');
startBtn.disabled = false;
startBtn.textContent = 'Start DFS';
}
function resetGrid() {
// Clear all cells' classes except 'start' and 'end'
for (let row = 0; row < ROWS; row++) {
for (let col = 0; col < COLS; col++) {
const cell = grid[row][col];
cell.classList.remove('visited', 'path');
}
}
}
BFS搜索算法
const ROWS = 20;
const COLS = 20;
let grid = [];
let startCell = null;
let endCell = null;
let isRunning = false;
function createGrid() {
const gridContainer = document.getElementById('grid-container');
for (let row = 0; row < ROWS; row++) {
const rowArray = [];
for (let col = 0; col < COLS; col++) {
const cell = document.createElement('div');
cell.classList.add('cell');
cell.dataset.row = row;
cell.dataset.col = col;
cell.addEventListener('click', handleCellClick);
gridContainer.appendChild(cell);
rowArray.push(cell);
}
grid.push(rowArray);
}
}
function handleCellClick(event) {
const cell = event.target;
if (!startCell) {
cell.classList.add('start');
startCell = cell;
} else if (!endCell && cell !== startCell) {
cell.classList.add('end');
endCell = cell;
}
}
async function bfs(start, end) {
if (isRunning) return;
isRunning = true;
resetGrid();
const queue = [start];
const visited = new Set();
const parentMap = {};
let found = false;
while (queue.length > 0) {
const current = queue.shift();
const [row, col] = current;
if (visited.has(`${row},${col}`)) continue;
visited.add(`${row},${col}`);
grid[row][col].classList.add('visited');
if (grid[row][col] === endCell) {
found = true;
break;
}
const neighbors = getNeighbors(row, col);
for (const neighbor of neighbors) {
const [nRow, nCol] = neighbor;
if (!visited.has(`${nRow},${nCol}`)) {
queue.push([nRow, nCol]);
parentMap[`${nRow},${nCol}`] = [row, col];
}
}
// Animation delay
await sleep(100); // Wait for 100ms between steps
}
if (found) {
await reconstructPath(parentMap, [parseInt(endCell.dataset.row), parseInt(endCell.dataset.col)]);
}
isRunning = false;
enableStartButton();
}
function getNeighbors(row, col) {
const neighbors = [];
const directions = [
[0, 1], // right
[1, 0], // down
[0, -1], // left
[-1, 0] // up
];
for (const [dr, dc] of directions) {
const newRow = row + dr;
const newCol = col + dc;
if (newRow >= 0 && newRow < ROWS && newCol >= 0 && newCol < COLS) {
neighbors.push([newRow, newCol]);
}
}
return neighbors;
}
async function reconstructPath(parentMap, end) {
let path = [];
let current = end.join(',');
// Build the path from end to start
while (current) {
const [row, col] = current.split(',').map(Number);
path.unshift([row, col]);
current = parentMap[current] ? parentMap[current].join(',') : null;
}
// Highlight each cell in the path with a delay
for (const [row, col] of path) {
if (grid[row][col] !== startCell && grid[row][col] !== endCell) {
grid[row][col].classList.add('path');
await sleep(100); // Wait for 100ms between highlighting each cell
}
}
}
document.getElementById('start-btn').addEventListener('click', async () => {
disableStartButton();
if (startCell && endCell) {
await bfs([parseInt(startCell.dataset.row), parseInt(startCell.dataset.col)], [parseInt(endCell.dataset.row), parseInt(endCell.dataset.col)]);
} else {
alert('Please select both start and end cells.');
enableStartButton();
}
});
createGrid();
// Helper function to create a delay
function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
function disableStartButton() {
const startBtn = document.getElementById('start-btn');
startBtn.disabled = true;
startBtn.textContent = 'Running...';
}
function enableStartButton() {
const startBtn = document.getElementById('start-btn');
startBtn.disabled = false;
startBtn.textContent = 'Start BFS';
}
function resetGrid() {
// Clear all cells' classes except 'start' and 'end'
for (let row = 0; row < ROWS; row++) {
for (let col = 0; col < COLS; col++) {
const cell = grid[row][col];
cell.classList.remove('visited', 'path');
}
}
}
A*搜索算法
const ROWS = 20;
const COLS = 20;
let grid = [];
let startCell = null;
let endCell = null;
let isRunning = false;
function createGrid() {
const gridContainer = document.getElementById('grid-container');
for (let row = 0; row < ROWS; row++) {
const rowArray = [];
for (let col = 0; col < COLS; col++) {
const cell = document.createElement('div');
cell.classList.add('cell');
cell.dataset.row = row;
cell.dataset.col = col;
cell.addEventListener('click', handleCellClick);
gridContainer.appendChild(cell);
rowArray.push(cell);
}
grid.push(rowArray);
}
}
function handleCellClick(event) {
const cell = event.target;
if (!startCell) {
cell.classList.add('start');
startCell = cell;
} else if (!endCell && cell !== startCell) {
cell.classList.add('end');
endCell = cell;
}
}
async function aStar(start, end) {
if (isRunning) return;
isRunning = true;
resetGrid();
const openSet = new PriorityQueue();
const cameFrom = {};
const gScore = {};
const fScore = {};
gScore[start.join(',')] = 0;
fScore[start.join(',')] = heuristic(start, end);
openSet.enqueue(start, fScore[start.join(',')] || Infinity);
while (!openSet.isEmpty()) {
const current = openSet.dequeue().element;
const [row, col] = current;
if (current.join(',') === end.join(',')) {
await reconstructPath(cameFrom, end);
isRunning = false;
enableStartButton();
return;
}
grid[row][col].classList.add('visited');
const neighbors = getNeighbors(row, col);
for (const neighbor of neighbors) {
const [nRow, nCol] = neighbor;
const tentativeGScore = gScore[current.join(',')] + 1;
if (tentativeGScore < (gScore[neighbor.join(',')] || Infinity)) {
cameFrom[neighbor.join(',')] = current;
gScore[neighbor.join(',')] = tentativeGScore;
fScore[neighbor.join(',')] = tentativeGScore + heuristic(neighbor, end);
if (!openSet.contains(neighbor)) {
openSet.enqueue(neighbor, fScore[neighbor.join(',')] || Infinity);
}
}
}
// Animation delay
await sleep(100); // Wait for 1 second between steps
}
alert('No path found!');
isRunning = false;
enableStartButton();
}
function heuristic(a, b) {
const [aRow, aCol] = a;
const [bRow, bCol] = b;
const selectedHeuristic = document.getElementById('heuristic-select').value;
switch (selectedHeuristic) {
case 'manhattan':
return Math.abs(aRow - bRow) + Math.abs(aCol - bCol); // 曼哈顿距离
case 'chebyshev':
return Math.max(Math.abs(aRow - bRow), Math.abs(aCol - bCol)); // 切比雪夫距离
case 'euclidean':
return Math.sqrt(Math.pow(aRow - bRow, 2) + Math.pow(aCol - bCol, 2)); // 欧氏距离
default:
return Math.abs(aRow - bRow) + Math.abs(aCol - bCol); // 默认为曼哈顿距离
}
}
function getNeighbors(row, col) {
const neighbors = [];
const directions = [
[0, 1], // right
[1, 0], // down
[0, -1], // left
[-1, 0] // up
];
for (const [dr, dc] of directions) {
const newRow = row + dr;
const newCol = col + dc;
if (newRow >= 0 && newRow < ROWS && newCol >= 0 && newCol < COLS) {
neighbors.push([newRow, newCol]);
}
}
return neighbors;
}
async function reconstructPath(cameFrom, end) {
let path = [];
let current = end.join(',');
while (current) {
const [row, col] = current.split(',').map(Number);
path.unshift([row, col]);
if (cameFrom[current] === undefined || cameFrom[current].join(',') === startCell.dataset.row + ',' + startCell.dataset.col) {
break;
}
current = cameFrom[current].join(',');
}
for (const [row, col] of path) {
if (grid[row][col] !== startCell && grid[row][col] !== endCell) {
grid[row][col].classList.add('path');
}
await sleep(100); // Add delay to show each step in the path
}
}
document.getElementById('start-btn').addEventListener('click', async () => {
disableStartButton();
if (startCell && endCell) {
await aStar(
[parseInt(startCell.dataset.row), parseInt(startCell.dataset.col)],
[parseInt(endCell.dataset.row), parseInt(endCell.dataset.col)]
);
} else {
alert('Please select both start and end cells.');
enableStartButton();
}
});
createGrid();
// Helper function to create a delay
function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
class PriorityQueue {
constructor() {
this.elements = [];
}
enqueue(element, priority) {
this.elements.push({ element, priority });
this.elements.sort((a, b) => a.priority - b.priority);
}
dequeue() {
return this.elements.shift();
}
isEmpty() {
return this.elements.length === 0;
}
contains(element) {
return this.elements.some(e => e.element.join(',') === element.join(','));
}
}
function disableStartButton() {
const startBtn = document.getElementById('start-btn');
startBtn.disabled = true;
startBtn.textContent = 'Running...';
const selectedHeuristic = document.getElementById('heuristic-select');
selectedHeuristic.disabled = true;
}
function enableStartButton() {
const startBtn = document.getElementById('start-btn');
startBtn.disabled = false;
startBtn.textContent = 'Start A*';
const selectedHeuristic = document.getElementById('heuristic-select');
selectedHeuristic.disabled = false;
}
function resetGrid() {
// Clear all cells' classes except 'start' and 'end'
for (let row = 0; row < ROWS; row++) {
for (let col = 0; col < COLS; col++) {
const cell = grid[row][col];
cell.classList.remove('visited', 'path');
}
}
}
贪婪最佳优先搜索算法
const ROWS = 20;
const COLS = 20;
let grid = [];
let startCell = null;
let endCell = null;
let isRunning = false;
function createGrid() {
const gridContainer = document.getElementById('grid-container');
for (let row = 0; row < ROWS; row++) {
const rowArray = [];
for (let col = 0; col < COLS; col++) {
const cell = document.createElement('div');
cell.classList.add('cell');
cell.dataset.row = row;
cell.dataset.col = col;
cell.addEventListener('click', handleCellClick);
gridContainer.appendChild(cell);
rowArray.push(cell);
}
grid.push(rowArray);
}
}
function handleCellClick(event) {
const cell = event.target;
if (!startCell) {
cell.classList.add('start');
startCell = cell;
} else if (!endCell && cell !== startCell) {
cell.classList.add('end');
endCell = cell;
}
}
async function greedyBestFirstSearch(start, end) {
if (isRunning) return;
isRunning = true;
resetGrid();
const openSet = new PriorityQueue();
const cameFrom = {};
openSet.enqueue(start, heuristic(start, end));
while (!openSet.isEmpty()) {
const current = openSet.dequeue().element;
const [row, col] = current;
if (current.join(',') === end.join(',')) {
await reconstructPath(cameFrom, end);
isRunning = false;
enableStartButton();
return;
}
grid[row][col].classList.add('visited');
const neighbors = getNeighbors(row, col);
for (const neighbor of neighbors) {
const [nRow, nCol] = neighbor;
if (!cameFrom[neighbor.join(',')] && !openSet.contains(neighbor)) {
cameFrom[neighbor.join(',')] = current;
openSet.enqueue(neighbor, heuristic(neighbor, end));
}
}
// Animation delay
await sleep(100); // Wait for 1 second between steps
}
alert('No path found!');
isRunning = false;
enableStartButton();
}
function heuristic(a, b) {
const [aRow, aCol] = a;
const [bRow, bCol] = b;
const selectedHeuristic = document.getElementById('heuristic-select').value;
switch (selectedHeuristic) {
case 'manhattan':
return Math.abs(aRow - bRow) + Math.abs(aCol - bCol); // 曼哈顿距离
case 'chebyshev':
return Math.max(Math.abs(aRow - bRow), Math.abs(aCol - bCol)); // 切比雪夫距离
case 'euclidean':
return Math.sqrt(Math.pow(aRow - bRow, 2) + Math.pow(aCol - bCol, 2)); // 欧氏距离
default:
return Math.abs(aRow - bRow) + Math.abs(aCol - bCol); // 默认为曼哈顿距离
}
}
function getNeighbors(row, col) {
const neighbors = [];
const directions = [
[0, 1], // right
[1, 0], // down
[0, -1], // left
[-1, 0] // up
];
for (const [dr, dc] of directions) {
const newRow = row + dr;
const newCol = col + dc;
if (newRow >= 0 && newRow < ROWS && newCol >= 0 && newCol < COLS) {
neighbors.push([newRow, newCol]);
}
}
return neighbors;
}
async function reconstructPath(cameFrom, end) {
let path = [];
let current = end.join(',');
while (current) {
const [row, col] = current.split(',').map(Number);
path.unshift([row, col]);
if (cameFrom[current] === undefined || cameFrom[current].join(',') === startCell.dataset.row + ',' + startCell.dataset.col) {
break;
}
current = cameFrom[current].join(',');
}
for (const [row, col] of path) {
if (grid[row][col] !== startCell && grid[row][col] !== endCell) {
grid[row][col].classList.add('path');
}
await sleep(100); // Add delay to show each step in the path
}
}
document.getElementById('start-btn').addEventListener('click', async () => {
disableStartButton();
if (startCell && endCell) {
await greedyBestFirstSearch(
[parseInt(startCell.dataset.row), parseInt(startCell.dataset.col)],
[parseInt(endCell.dataset.row), parseInt(endCell.dataset.col)]
);
} else {
alert('Please select both start and end cells.');
enableStartButton();
}
});
createGrid();
// Helper function to create a delay
function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
class PriorityQueue {
constructor() {
this.elements = [];
}
enqueue(element, priority) {
this.elements.push({ element, priority });
this.elements.sort((a, b) => a.priority - b.priority);
}
dequeue() {
return this.elements.shift();
}
isEmpty() {
return this.elements.length === 0;
}
contains(element) {
return this.elements.some(e => e.element.join(',') === element.join(','));
}
}
function disableStartButton() {
const startBtn = document.getElementById('start-btn');
startBtn.disabled = true;
startBtn.textContent = 'Running...';
const selectedHeuristic = document.getElementById('heuristic-select');
selectedHeuristic.disabled = true;
}
function enableStartButton() {
const startBtn = document.getElementById('start-btn');
startBtn.disabled = false;
startBtn.textContent = 'Start Greedy Best-First Search';
const selectedHeuristic = document.getElementById('heuristic-select');
selectedHeuristic.disabled = false;
}
function resetGrid() {
// Clear all cells' classes except 'start' and 'end'
for (let row = 0; row < ROWS; row++) {
for (let col = 0; col < COLS; col++) {
const cell = grid[row][col];
cell.classList.remove('visited', 'path');
}
}
}
双向搜索-DFS搜索算法
const ROWS = 20;
const COLS = 20;
let grid = [];
let startCell = null;
let endCell = null;
let isRunning = false;
function createGrid() {
const gridContainer = document.getElementById('grid-container');
for (let row = 0; row < ROWS; row++) {
const rowArray = [];
for (let col = 0; col < COLS; col++) {
const cell = document.createElement('div');
cell.classList.add('cell');
cell.dataset.row = row;
cell.dataset.col = col;
cell.addEventListener('click', handleCellClick);
gridContainer.appendChild(cell);
rowArray.push(cell);
}
grid.push(rowArray);
}
}
function handleCellClick(event) {
const cell = event.target;
if (!startCell) {
cell.classList.add('start');
startCell = cell;
} else if (!endCell && cell !== startCell) {
cell.classList.add('end');
endCell = cell;
}
}
async function bidirectionalSearch(start, end) {
if (isRunning) return;
isRunning = true;
resetGrid();
const stackStart = [start];
const stackEnd = [end];
const visitedStart = new Set();
const visitedEnd = new Set();
const parentMapStart = {};
const parentMapEnd = {};
let found = false;
const meetingPointRef = { meetingPoint: null };
while (stackStart.length > 0 && stackEnd.length > 0) {
if (await dfsStep(stackStart, visitedStart, parentMapStart, 'visited-start', end, visitedEnd, meetingPointRef)) {
found = true;
break;
}
if (await dfsStep(stackEnd, visitedEnd, parentMapEnd, 'visited-end', start, visitedStart, meetingPointRef)) {
found = true;
break;
}
// Animation delay
await sleep(100); // Wait for 100ms between steps
}
if (found) {
await reconstructPath(parentMapStart, parentMapEnd, start, end, meetingPointRef.meetingPoint);
}
isRunning = false;
enableStartButton();
}
async function dfsStep(stack, visited, parentMap, visitClass, target, otherVisited, meetingPointRef) {
if (stack.length === 0) return false;
const current = stack.pop();
const [row, col] = current;
if (visited.has(`${row},${col}`)) return false;
visited.add(`${row},${col}`);
grid[row][col].classList.add(visitClass);
if (otherVisited.has(`${row},${col}`)) {
meetingPointRef.meetingPoint = [row, col];
return true;
}
const neighbors = getNeighbors(row, col);
for (const neighbor of neighbors) {
const [nRow, nCol] = neighbor;
if (!visited.has(`${nRow},${nCol}`)) {
stack.push([nRow, nCol]);
parentMap[`${nRow},${nCol}`] = [row, col];
}
}
return false;
}
function getNeighbors(row, col) {
const neighbors = [];
const directions = [
[0, 1], // right
[1, 0], // down
[0, -1], // left
[-1, 0] // up
];
for (const [dr, dc] of directions) {
const newRow = row + dr;
const newCol = col + dc;
if (newRow >= 0 && newRow < ROWS && newCol >= 0 && newCol < COLS) {
neighbors.push([newRow, newCol]);
}
}
return neighbors;
}
async function reconstructPath(parentMapStart, parentMapEnd, start, end, meetingPoint) {
if (!meetingPoint) return;
let pathStart = [];
let pathEnd = [];
// Build the path from start to meeting point
let currentStart = meetingPoint.join(',');
while (currentStart !== start.join(',')) {
pathStart.push(currentStart.split(',').map(Number));
currentStart = parentMapStart[currentStart]?.join(',') || start.join(',');
}
pathStart.push(start);
// Build the path from end to meeting point
let currentEnd = meetingPoint.join(',');
while (currentEnd !== end.join(',')) {
pathEnd.push(currentEnd.split(',').map(Number));
currentEnd = parentMapEnd[currentEnd]?.join(',') || end.join(',');
}
pathEnd.push(end);
// Reverse the pathStart to get the correct order from start to meeting point
pathStart.reverse();
// Combine the paths and remove duplicate meeting point
const fullPath = [...pathStart, ...pathEnd.slice(1)];
// Highlight each cell in the path with a delay
for (const [row, col] of fullPath) {
const cell = grid[row][col];
if (cell !== startCell && cell !== endCell) {
cell.classList.add('path');
await sleep(100); // Wait for 100ms between highlighting each cell
}
}
}
document.getElementById('start-btn').addEventListener('click', async () => {
disableStartButton();
if (startCell && endCell) {
await bidirectionalSearch(
[parseInt(startCell.dataset.row), parseInt(startCell.dataset.col)],
[parseInt(endCell.dataset.row), parseInt(endCell.dataset.col)]
);
} else {
alert('Please select both start and end cells.');
enableStartButton();
}
});
createGrid();
// Helper function to create a delay
function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
function disableStartButton() {
const startBtn = document.getElementById('start-btn');
startBtn.disabled = true;
startBtn.textContent = 'Running...';
}
function enableStartButton() {
const startBtn = document.getElementById('start-btn');
startBtn.disabled = false;
startBtn.textContent = 'Start DFS';
}
function resetGrid() {
// Clear all cells' classes except 'start' and 'end'
for (let row = 0; row < ROWS; row++) {
for (let col = 0; col < COLS; col++) {
const cell = grid[row][col];
cell.classList.remove('visited', 'path', 'visited-start', 'visited-end');
}
}
}
效果展示
BFS搜索
DFS搜索
A星-曼哈顿距离
贪婪最佳优先搜索
双向搜索-DFS
IDA*(迭代加深搜索A*)
源码下载
完整代码在gitee上,链接:搜索算法网页演示