-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #56 from monkey0722/feture/algorithm
Implement Various Data Structures and Algorithms with Node 22 Upgrade
- Loading branch information
Showing
14 changed files
with
1,535 additions
and
9 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1 @@ | ||
20.18.1 | ||
22.12.0 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,102 @@ | ||
import {Knapsack} from './knapsack'; | ||
|
||
describe('Knapsack', () => { | ||
describe('solve (0-1 Knapsack)', () => { | ||
test('should solve the 0-1 knapsack problem correctly', () => { | ||
const items = [ | ||
{weight: 2, value: 3}, | ||
{weight: 3, value: 4}, | ||
{weight: 4, value: 5}, | ||
{weight: 5, value: 6}, | ||
]; | ||
const capacity = 5; | ||
const result = Knapsack.solve(items, capacity); | ||
expect(result.maxValue).toBe(7); | ||
expect(result.selectedItems).toEqual([true, true, false, false]); | ||
}); | ||
test('should return zero when capacity is zero', () => { | ||
const items = [ | ||
{weight: 2, value: 3}, | ||
{weight: 3, value: 4}, | ||
]; | ||
const capacity = 0; | ||
const result = Knapsack.solve(items, capacity); | ||
expect(result.maxValue).toBe(0); | ||
expect(result.selectedItems).toEqual([false, false]); | ||
}); | ||
|
||
test('should solve correctly when items have zero weight', () => { | ||
const items = [ | ||
{weight: 0, value: 5}, | ||
{weight: 2, value: 3}, | ||
]; | ||
const capacity = 2; | ||
const result = Knapsack.solve(items, capacity); | ||
expect(result.maxValue).toBe(8); | ||
expect(result.selectedItems).toEqual([true, true]); | ||
}); | ||
test('should solve correctly when items have zero value', () => { | ||
const items = [ | ||
{weight: 1, value: 0}, | ||
{weight: 2, value: 0}, | ||
]; | ||
const capacity = 3; | ||
const result = Knapsack.solve(items, capacity); | ||
expect(result.maxValue).toBe(0); | ||
expect(result.selectedItems).toEqual([false, false]); | ||
}); | ||
test('should throw an error for negative weight or value', () => { | ||
const items = [ | ||
{weight: -2, value: 3}, | ||
{weight: 3, value: 4}, | ||
]; | ||
const capacity = 5; | ||
expect(() => Knapsack.solve(items, capacity)).toThrowError( | ||
'Item weights and values must be non-negative' | ||
); | ||
}); | ||
}); | ||
|
||
describe('solveFractional (Fractional Knapsack)', () => { | ||
test('should return zero when capacity is zero', () => { | ||
const items = [ | ||
{weight: 10, value: 60}, | ||
{weight: 20, value: 100}, | ||
]; | ||
const capacity = 0; | ||
const result = Knapsack.solveFractional(items, capacity); | ||
expect(result.maxValue).toBe(0); | ||
expect(result.fractions).toEqual([0, 0]); | ||
}); | ||
test('should solve correctly when items have zero weight', () => { | ||
const items = [ | ||
{weight: 0, value: 50}, | ||
{weight: 10, value: 60}, | ||
]; | ||
const capacity = 10; | ||
const result = Knapsack.solveFractional(items, capacity); | ||
expect(result.maxValue).toBe(110); | ||
expect(result.fractions).toEqual([1, 1]); | ||
}); | ||
test('should solve correctly when items have zero value', () => { | ||
const items = [ | ||
{weight: 5, value: 0}, | ||
{weight: 10, value: 0}, | ||
]; | ||
const capacity = 15; | ||
const result = Knapsack.solveFractional(items, capacity); | ||
expect(result.maxValue).toBe(0); | ||
expect(result.fractions).toEqual([0, 0]); | ||
}); | ||
test('should throw an error for negative weight or value', () => { | ||
const items = [ | ||
{weight: 5, value: -10}, | ||
{weight: 10, value: 50}, | ||
]; | ||
const capacity = 15; | ||
expect(() => Knapsack.solveFractional(items, capacity)).toThrowError( | ||
'Item weights and values must be non-negative' | ||
); | ||
}); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,122 @@ | ||
interface Item { | ||
weight: number; | ||
value: number; | ||
} | ||
|
||
export class Knapsack { | ||
/** | ||
* Solves the 0-1 knapsack problem. | ||
* @param items Array of items, each with a weight and value. | ||
* @param capacity The maximum weight capacity of the knapsack. | ||
* @returns An object containing the maximum value and the selection status of each item. | ||
* @throws {Error} If inputs are invalid. | ||
*/ | ||
static solve( | ||
items: Item[], | ||
capacity: number | ||
): { | ||
maxValue: number; | ||
selectedItems: boolean[]; | ||
} { | ||
if (capacity < 0) { | ||
throw new Error('Capacity must be non-negative'); | ||
} | ||
for (const item of items) { | ||
if (item.weight < 0 || item.value < 0) { | ||
throw new Error('Item weights and values must be non-negative'); | ||
} | ||
} | ||
|
||
const n = items.length; | ||
const dp: number[][] = Array.from({length: n + 1}, () => | ||
Array(capacity + 1).fill(0) | ||
); | ||
|
||
// Build the DP table | ||
for (let i = 1; i <= n; i++) { | ||
const item = items[i - 1]; | ||
for (let w = 0; w <= capacity; w++) { | ||
if (item.weight <= w) { | ||
dp[i][w] = Math.max( | ||
dp[i - 1][w], | ||
dp[i - 1][w - item.weight] + item.value | ||
); | ||
} else { | ||
dp[i][w] = dp[i - 1][w]; | ||
} | ||
} | ||
} | ||
|
||
// Trace back to find selected items | ||
const selectedItems: boolean[] = Array(n).fill(false); | ||
let remainingWeight = capacity; | ||
for (let i = n; i > 0; i--) { | ||
if (dp[i][remainingWeight] !== dp[i - 1][remainingWeight]) { | ||
selectedItems[i - 1] = true; | ||
remainingWeight -= items[i - 1].weight; | ||
} | ||
} | ||
|
||
return { | ||
maxValue: dp[n][capacity], | ||
selectedItems, | ||
}; | ||
} | ||
|
||
/** | ||
* Solves the fractional knapsack problem. | ||
* @param items Array of items, each with a weight and value. | ||
* @param capacity The maximum weight capacity of the knapsack. | ||
* @returns An object containing the maximum value and the selection fractions of each item. | ||
* @throws {Error} If inputs are invalid. | ||
*/ | ||
static solveFractional( | ||
items: Item[], | ||
capacity: number | ||
): { | ||
maxValue: number; | ||
fractions: number[]; | ||
} { | ||
if (capacity < 0) { | ||
throw new Error('Capacity must be non-negative'); | ||
} | ||
for (const item of items) { | ||
if (item.weight < 0 || item.value < 0) { | ||
throw new Error('Item weights and values must be non-negative'); | ||
} | ||
} | ||
|
||
const n = items.length; | ||
const sortedItems = items | ||
.map((item, index) => ({ | ||
...item, | ||
ratio: item.weight === 0 ? Infinity : item.value / item.weight, | ||
index, // Preserve original index | ||
})) | ||
.sort((a, b) => b.ratio - a.ratio); // Sort by value-to-weight ratio | ||
|
||
let remainingCapacity = capacity; | ||
let totalValue = 0; | ||
const fractions = Array(n).fill(0); | ||
|
||
for (const item of sortedItems) { | ||
if (item.value === 0 || remainingCapacity <= 0) { | ||
continue; // Skip zero value items or if no capacity is left | ||
} | ||
if (remainingCapacity >= item.weight) { | ||
fractions[item.index] = 1; | ||
totalValue += item.value; | ||
remainingCapacity -= item.weight; | ||
} else { | ||
const fraction = remainingCapacity / item.weight; | ||
fractions[item.index] = fraction; | ||
totalValue += item.value * fraction; | ||
break; | ||
} | ||
} | ||
return { | ||
maxValue: totalValue, | ||
fractions, | ||
}; | ||
} | ||
} |
57 changes: 57 additions & 0 deletions
57
algorithms/search/bellman-ford-search/bellmanFordSearch.test.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,57 @@ | ||
import {BellmanFord} from './bellmanFordSearch'; | ||
|
||
describe('BellmanFord', () => { | ||
test('should find shortest paths correctly for a valid graph', () => { | ||
const edges = [ | ||
{source: 0, target: 1, weight: 4}, | ||
{source: 0, target: 2, weight: 5}, | ||
{source: 1, target: 2, weight: -3}, | ||
{source: 1, target: 3, weight: 6}, | ||
{source: 2, target: 3, weight: 1}, | ||
]; | ||
const result = BellmanFord.findShortestPaths(4, edges, 0); | ||
expect(result).toEqual([0, 4, 1, 2]); | ||
}); | ||
test('should detect negative weight cycle and return null', () => { | ||
const edges = [ | ||
{source: 0, target: 1, weight: 1}, | ||
{source: 1, target: 2, weight: 1}, | ||
{source: 2, target: 0, weight: -3}, | ||
]; | ||
const result = BellmanFord.findShortestPaths(3, edges, 0); | ||
expect(result).toBeNull(); | ||
}); | ||
test('should return null when reconstructing path for unreachable vertex', () => { | ||
const edges = [ | ||
{source: 0, target: 1, weight: 4}, | ||
{source: 1, target: 2, weight: 3}, | ||
]; | ||
const path = BellmanFord.reconstructPath(4, edges, 0, 3); | ||
expect(path).toBeNull(); | ||
}); | ||
test('should throw error for invalid vertex count', () => { | ||
const edges = [ | ||
{source: 0, target: 1, weight: 4}, | ||
{source: 1, target: 2, weight: 3}, | ||
]; | ||
expect(() => BellmanFord.findShortestPaths(0, edges, 0)).toThrowError( | ||
'Number of vertices must be positive' | ||
); | ||
}); | ||
test('should throw error for invalid edge vertices', () => { | ||
const edges = [{source: 0, target: 5, weight: 4}]; | ||
expect(() => BellmanFord.findShortestPaths(4, edges, 0)).toThrowError( | ||
'Edge contains an invalid vertex' | ||
); | ||
}); | ||
test('should handle large graph with no negative weight cycles', () => { | ||
const edges = Array.from({length: 100}, (_, i) => ({ | ||
source: i, | ||
target: (i + 1) % 100, | ||
weight: 1, | ||
})); | ||
const result = BellmanFord.findShortestPaths(100, edges, 0); | ||
expect(result).toHaveLength(100); | ||
expect(result![99]).toBe(99); | ||
}); | ||
}); |
Oops, something went wrong.