Skip to content

Commit

Permalink
feat: add algorithm demos
Browse files Browse the repository at this point in the history
  • Loading branch information
veaba committed Jan 2, 2025
1 parent 4375ae8 commit 6cecbbf
Show file tree
Hide file tree
Showing 33 changed files with 530 additions and 35 deletions.
18 changes: 18 additions & 0 deletions demos/html/curve.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<!DOCTYPE html>
<html>
<head>
<style>
.curved-shape {
width: 200px;
height: 200px;
background-color: #3498db;
clip-path: path('M 0,0 Q 75,50 150,0');
}
</style>
</head>
<body>

<div class="curved-shape"></div>

</body>
</html>
29 changes: 29 additions & 0 deletions demos/js/algorithm/normal/backtrack-combine.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
/**
* 回溯算法 -组合问题
* 回溯算法是一种通过尝试所有可能的路径来解决问题的算法策略。在遇到不可行的路径时,
* 它会回退到上一个决策点,尝试其他路径,直到找到解决方案或遍历完所有可能的路径。
*/
function combine(n, k) {
const result = [];
const path = [];

function backtrack(start) {
if (path.length === k) {
result.push([...path]);
return;
}
for (let i = start; i <= n; i++) {
path.push(i);
backtrack(i + 1);
path.pop();
}
}

backtrack(1);
return result;
}

// 测试
const n = 4;
const k = 2;
console.log(combine(n, k));
33 changes: 33 additions & 0 deletions demos/js/algorithm/normal/backtrack-full-sort.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
/**
* 回溯算法 - 全排列
* 回溯算法是一种通过尝试所有可能的路径来解决问题的算法策略。在遇到不可行的路径时,
* 它会回退到上一个决策点,尝试其他路径,直到找到解决方案或遍历完所有可能的路径。
* 给定一个不包含重复数字的数组,返回所有可能的全排列。
*/
function permute(nums) {
const result = [];
const used = new Array(nums.length).fill(false);
const path = [];

function backtrack() {
if (path.length === nums.length) {
result.push([...path]);
return;
}
for (let i = 0; i < nums.length; i++) {
if (used[i]) continue;
path.push(nums[i]);
used[i] = true;
backtrack();
path.pop();
used[i] = false;
}
}

backtrack();
return result;
}

// 测试
const nums = [1, 2, 3];
console.log(permute(nums));
61 changes: 61 additions & 0 deletions demos/js/algorithm/normal/backtrack-solveSudoku.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
/**
* 回溯算法 - 解数独
* 回溯算法是一种通过尝试所有可能的路径来解决问题的算法策略。在遇到不可行的路径时,
* 它会回退到上一个决策点,尝试其他路径,直到找到解决方案或遍历完所有可能的路径。
*/
function solveSudoku(board) {
function isValid(row, col, num) {
// 检查行
for (let i = 0; i < 9; i++) {
if (board[row][i] === num) return false;
}
// 检查列
for (let i = 0; i < 9; i++) {
if (board[i][col] === num) return false;
}
// 检查 3x3 子棋盘
const startRow = Math.floor(row / 3) * 3;
const startCol = Math.floor(col / 3) * 3;
for (let i = startRow; i < startRow + 3; i++) {
for (let j = startCol; j < startCol + 3; j++) {
if (board[i][j] === num) return false;
}
}
return true;
}

function backtrack() {
for (let i = 0; i < 9; i++) {
for (let j = 0; j < 9; j++) {
if (board[i][j] === '.') {
for (let num = 1; num <= 9; num++) {
if (isValid(i, j, num)) {
board[i][j] = num.toString();
if (backtrack()) return true;
board[i][j] = '.';
}
}
return false;
}
}
}
return true;
}

backtrack();
return board;
}

// 测试
const board = [
['5', '3', '.', '.', '7', '.', '.', '.', '.'],
['6', '.', '.', '1', '9', '5', '.', '.', '.'],
['.', '9', '8', '.', '.', '.', '.', '6', '.'],
['8', '.', '.', '.', '6', '.', '.', '.', '3'],
['4', '.', '.', '8', '.', '3', '.', '.', '1'],
['7', '.', '.', '.', '2', '.', '.', '.', '6'],
['.', '6', '.', '.', '.', '.', '2', '8', '.'],
['.', '.', '.', '4', '1', '9', '.', '.', '5'],
['.', '.', '.', '.', '8', '.', '.', '7', '9'],
];
console.log(solveSudoku(board));
27 changes: 27 additions & 0 deletions demos/js/algorithm/normal/divide-maxSubArray.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
/**
* 分治算法(Divide and Conquer Algorithm)是一种常用的算法策略,
* 它将一个复杂的问题分解成多个规模较小、相互独立且形式相同的子问题,
* 然后分别解决这些子问题,最后将子问题的解合并起来得到原问题的解。
*
* 给定一个整数数组 nums,找到一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。
*/

function maxSubArray(nums) {
if (nums.length === 1) {
return nums[0];
}

let maxEndingHere = nums[0];
let maxSoFar = nums[0];

for (let i = 1; i < nums.length; i++) {
maxEndingHere = Math.max(nums[i], maxEndingHere + nums[i]);
maxSoFar = Math.max(maxSoFar, maxEndingHere);
}

return maxSoFar;
}

// 测试
const nums = [-2, 1, -3, 4, -1, 2, 3, -5, 4];
console.log(maxSubArray(nums)); // 输出 6
47 changes: 47 additions & 0 deletions demos/js/algorithm/normal/divide-merge-sort.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
/**
* 分治算法(Divide and Conquer Algorithm)是一种常用的算法策略,
* 它将一个复杂的问题分解成多个规模较小、相互独立且形式相同的子问题,
* 然后分别解决这些子问题,最后将子问题的解合并起来得到原问题的解。
*
* 归并排序是一种典型的分治算法,它将一个数组分成两个子数组,分别对这两个子数组进行排序,然后将排好序的子数组合并成一个有序的数组。
*/

function mergeSort(arr) {
if (arr.length <= 1) {
return arr;
}

// 分割数组
const mid = Math.floor(arr.length / 2);
const left = arr.slice(0, mid);
const right = arr.slice(mid);

// 递归排序子数组
const sortedLeft = mergeSort(left);
const sortedRight = mergeSort(right);

// 合并两个有序子数组
return merge(sortedLeft, sortedRight);
}

function merge(left, right) {
let result = [];
let leftIndex = 0;
let rightIndex = 0;

while (leftIndex < left.length && rightIndex < right.length) {
if (left[leftIndex] < right[rightIndex]) {
result.push(left[leftIndex]);
leftIndex++;
} else {
result.push(right[rightIndex]);
rightIndex++;
}
}

return result.concat(left.slice(leftIndex)).concat(right.slice(rightIndex));
}

// 测试
const array = [38, 27, 43, 3, 9, 82, 10];
console.log(mergeSort(array));
35 changes: 35 additions & 0 deletions demos/js/algorithm/normal/divide-quick-sort.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/**
* 分治算法(Divide and Conquer Algorithm)是一种常用的算法策略,
* 它将一个复杂的问题分解成多个规模较小、相互独立且形式相同的子问题,
* 然后分别解决这些子问题,最后将子问题的解合并起来得到原问题的解。
*
* 快速排序也是基于分治思想,通过选择一个基准元素,将数组分为两部分,使得左边部分的元素都小于等于基准元素,右边部分的元素都大于等于基准元素,然后分别对左右两部分进行排序。
*/

function quickSort(arr) {
if (arr.length <= 1) {
return arr;
}

// 选择基准元素
const pivot = arr[Math.floor(arr.length / 2)];
const left = [];
const right = [];
const equal = [];

for (let num of arr) {
if (num < pivot) {
left.push(num);
} else if (num > pivot) {
right.push(num);
} else {
equal.push(num);
}
}

return [...quickSort(left),...equal,...quickSort(right)];
}

// 测试
const array2 = [38, 27, 43, 3, 9, 82, 10];
console.log(quickSort(array2));
21 changes: 21 additions & 0 deletions demos/js/algorithm/normal/dynamic-climb-start.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
/**
* 动态规划(Dynamic Programming,DP)是一种通过把原问题分解为相对简单的子问题,
* 并保存子问题的解来避免重复计算,从而解决复杂问题的算法策略。
*/


function climbStairs(n) {
if (n <= 2) {
return n;
}
let dp = new Array(n + 1);
dp[1] = 1;
dp[2] = 2;
for (let i = 3; i <= n; i++) {
dp[i] = dp[i - 1] + dp[i - 2];
}
return dp[n];
}

// 测试
console.log(climbStairs(5));
23 changes: 23 additions & 0 deletions demos/js/algorithm/normal/dynamic-fibonacci.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/**
* 动态规划(Dynamic Programming,DP)是一种通过把原问题分解为相对简单的子问题,
* 并保存子问题的解来避免重复计算,从而解决复杂问题的算法策略。
*
* 0 - 1 背包问题:有 n 个物品,每个物品有重量 weights 和价值 values,背包的容量为 capacity。每个物品只能使用一次,求能装入背包的最大价值。
*/


function fibonacci(n) {
if (n <= 1) {
return n;
}
let dp = new Array(n + 1);
dp[0] = 0;
dp[1] = 1;
for (let i = 2; i <= n; i++) {
dp[i] = dp[i - 1] + dp[i - 2];
}
return dp[n];
}

// 测试
console.log(fibonacci(10));
28 changes: 28 additions & 0 deletions demos/js/algorithm/normal/dynamic-knapsack.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
/**
* 动态规划(Dynamic Programming,DP)是一种通过把原问题分解为相对简单的子问题,
* 并保存子问题的解来避免重复计算,从而解决复杂问题的算法策略。
*
* 假设你正在爬楼梯。需要 n 阶你才能到达楼顶。每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶呢?
*/

function knapsack(capacity, weights, values) {
let n = weights.length;
let dp = new Array(n + 1).fill(0).map(() => new Array(capacity + 1).fill(0));

for (let i = 1; i <= n; i++) {
for (let w = 0; w <= capacity; w++) {
if (weights[i - 1] > w) {
dp[i][w] = dp[i - 1][w];
} else {
dp[i][w] = Math.max(dp[i - 1][w], dp[i - 1][w - weights[i - 1]] + values[i - 1]);
}
}
}
return dp[n][capacity];
}

// 测试
let capacity = 5;
let weights = [2, 3, 4, 5];
let values = [3, 4, 5, 6];
console.log(knapsack(capacity, weights, values));
29 changes: 29 additions & 0 deletions demos/js/algorithm/normal/greedy-activity-selection.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
/**
* 贪心算法是一种在每一步选择中都采取当前状态下的最优决策,从而希望最终得到全局最优解的算法策略。
* 虽然贪心算法并不总是能得到全局最优解,但在许多问题上它可以提供高效且近似最优的解决方案。
* 假设有一组活动,每个活动都有开始时间和结束时间,你需要选择尽可能多的活动,使得这些活动之间没有时间冲突。
*/

function activitySelection(activities) {
activities.sort((a, b) => a.end - b.end);
let selected = [];
let lastEnd = -1;
for (let activity of activities) {
if (activity.start >= lastEnd) {
selected.push(activity);
lastEnd = activity.end;
}
}
return selected;
}

// 测试
const activities = [
{ start: 1, end: 3 },
{ start: 2, end: 4 },
{ start: 3, end: 5 },
{ start: 5, end: 7 },
{ start: 3, end: 8 }
];
const result = activitySelection(activities);
console.log(result);
19 changes: 19 additions & 0 deletions demos/js/algorithm/normal/greedy-coin-change.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
/**
* 贪心算法是一种在每一步选择中都采取当前状态下的最优决策,从而希望最终得到全局最优解的算法策略。
* 虽然贪心算法并不总是能得到全局最优解,但在许多问题上它可以提供高效且近似最优的解决方案。
* 假设你有无限数量的硬币,硬币面额为 [1, 5, 10, 25](美分),要找给顾客 amount 美分,求最少需要多少枚硬币。
*/

function coinChange(amount) {
const coins = [25, 10, 5, 1];
let count = 0;
for (let coin of coins) {
count += Math.floor(amount / coin);
amount %= coin;
}
return count;
}

// 测试
const amount = 63;
console.log(coinChange(amount)); // 输出 6
Loading

0 comments on commit 6cecbbf

Please sign in to comment.