diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 1d6763d..5a0bbea 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -1,16 +1,15 @@ { "name": "Graphs and Algorithmic Complexity", "image": "mcr.microsoft.com/devcontainers/base:alpine", - - "postCreateCommand": "sudo apk update && sudo apk add just typst gdb", - + "postCreateCommand": "sudo apk update && sudo apk add just typst gdb zig", "customizations": { "vscode": { "extensions": [ "ms-vscode.cpptools", "vadimcn.vscode-lldb", + "ziglang.vscode-zig", "myriad-dreamin.tinymist" ] } } -} +} \ No newline at end of file diff --git a/.github/workflows/c-code.yml b/.github/workflows/c-code.yml new file mode 100644 index 0000000..975ea8a --- /dev/null +++ b/.github/workflows/c-code.yml @@ -0,0 +1,18 @@ +name: c-code +on: + push: + branches: [ main ] + pull_request: + branches: [ main ] + +jobs: + clang-format: + name: Check if C code compiles and runs + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v4 + - name: Setup just + uses: extractions/setup-just@v2 + - name: Compile and run C code + run: just c-run diff --git a/.github/workflows/zig-code.yml b/.github/workflows/zig-code.yml new file mode 100644 index 0000000..dd8c6d6 --- /dev/null +++ b/.github/workflows/zig-code.yml @@ -0,0 +1,20 @@ +name: zig-code +on: + push: + branches: [ main ] + pull_request: + branches: [ main ] + +jobs: + clang-format: + name: Check if zig code compiles and runs + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v4 + - name: Setup zig + uses: mlugg/setup-zig@v1 + - name: Setup just + uses: extractions/setup-just@v2 + - name: Compile and run zig code + run: just z-run diff --git a/.github/workflows/zig-fmt.yml b/.github/workflows/zig-fmt.yml new file mode 100644 index 0000000..fcc29a3 --- /dev/null +++ b/.github/workflows/zig-fmt.yml @@ -0,0 +1,18 @@ +name: zig-fmt +on: + push: + branches: [ main ] + pull_request: + branches: [ main ] + +jobs: + clang-format: + name: Check if zig code is formatted + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v4 + - name: Setup zig + uses: mlugg/setup-zig@v1 + - name: Check zig code format + run: zig fmt --check ./code/zig/*.zig diff --git a/.gitignore b/.gitignore index 092412a..f7179fb 100644 --- a/.gitignore +++ b/.gitignore @@ -3,4 +3,7 @@ # C++ *.out -*.exe \ No newline at end of file +*.exe + +# Output +output/ diff --git a/README-pt.md b/README-pt.md index 19bc19f..e20ecf3 100644 --- a/README-pt.md +++ b/README-pt.md @@ -1,6 +1,8 @@ # Teoria dos Grafos e Complexidade Computacional [![CC0](https://img.shields.io/badge/License-CC0-lightgrey.svg)](https://creativecommons.org/publicdomain/zero/1.0/) +[![C code](https://img.shields.io/badge/code-C-blue)](./code/c) +[![Zig code](https://img.shields.io/badge/code-Zig-orange)](./code/zig) [![Rodar no Repl.it](https://repl.it/badge/github/storopoli/grafos-complexidade)](https://repl.it/github/storopoli/grafos-complexidade) [![Abrir no Gitpod](https://gitpod.io/button/open-in-gitpod.svg)](https://gitpod.io/#https://github.com/storopoli/grafos-complexidade) @@ -30,9 +32,15 @@ 1. Divisão e Conquista; e 1. Algoritmos Gulosos. +## Exemplos de Código +A principal linguagem de programação usada para os exemplos é C. +Também há exemplos em Zig. +Confira-os nos diretórios `code/c/` e `code/zig/`, respectivamente. + ## Dependências -- Compilador C/C++; e +- Compilador C/C++; +- (Opcional) compilador [Zig](https://ziglang.org); e - [`typst`](https://typst.app) para os slides. Os slides são gerados usando [Typst](https://typst.app) com GitHub Actions diff --git a/README.md b/README.md index 85134b2..8d333ae 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,8 @@ # Graph Theory and Computational Complexity [![CC0](https://img.shields.io/badge/License-CC0-lightgrey.svg)](https://creativecommons.org/publicdomain/zero/1.0/) +[![C code](https://img.shields.io/badge/code-C-blue)](./code/c) +[![Zig code](https://img.shields.io/badge/code-Zig-orange)](./code/zig) [![Run on Repl.it](https://repl.it/badge/github/storopoli/grafos-complexidade)](https://repl.it/github/storopoli/grafos-complexidade) [![Open in Gitpod](https://gitpod.io/button/open-in-gitpod.svg)](https://gitpod.io/#https://github.com/storopoli/grafos-complexidade) @@ -30,9 +32,16 @@ 1. Divide and Conquer; and 1. Greedy Algorithms. +## Code Examples + +The primary programming language used for examples is C. +Also there are [Zig](https://ziglang.org) examples. +Check them at `code/c/` and `code/zig/` directories, respectively. + ## Dependencies -- C/C++ Compiler; and +- C/C++ Compiler; +- (Optional) [Zig](https://ziglang.org) compiler; and - [`typst`](https://typst.app) for the slides. The slides are generated using [Typst](https://typst.app) with GitHub Actions diff --git a/code/01-graphs-adjacency-list.c b/code/01-graphs-adjacency-list.c deleted file mode 100644 index 9c74d76..0000000 --- a/code/01-graphs-adjacency-list.c +++ /dev/null @@ -1,32 +0,0 @@ -#include - -/** Function to create an adjacency list */ -void print_adj_list(int arr[5][5]) { - // Initialize to all zeros - int adj_list[5][5] = {{0}}; - - for (int i = 0; i < 5; ++i) { - printf("%d: ", i + 1); - for (int j = 0; j < 5; ++j) { - if (arr[i][j] == 1) { - adj_list[i][adj_list[i][0]] = j; // Store the neighbor in the list - printf("%d ", j + 1); - adj_list[i][0]++; // Increment the count of neighbors for this vertex - } - } - printf("\n"); - } -} - -/** Main Function */ -int main() { - // Given matrix - int arr[5][5] = { - {1, 0, 1, 0, 0}, {0, 1, 0, 0, 0}, {0, 1, 0, 0, 1}, - {0, 0, 0, 0, 1}, {1, 0, 0, 1, 0}, - }; - - print_adj_list(arr); - - return 0; -} diff --git a/code/01-graphs-adjacency-matrix.c b/code/01-graphs-adjacency-matrix.c deleted file mode 100644 index 71874ca..0000000 --- a/code/01-graphs-adjacency-matrix.c +++ /dev/null @@ -1,57 +0,0 @@ -#include - -/** N vertices and M edges */ -int N, M; - -/** Function to create an adjacency matrix */ -void create_adj_matrix(int adj[][N + 1], int arr[][2]) { - // Initialize all values to zero - for (int i = 0; i < N + 1; i++) { - for (int j = 0; j < N + 1; j++) { - adj[i][j] = 0; - } - } - - // Traverse the array of edges - for (int i = 0; i < M; i++) { - // Find X and Y of edges - int x = arr[i][0]; - int y = arr[i][1]; - - // Update value to 1 - adj[x][y] = 1; - adj[y][x] = 1; - } -} - -/** Function to print an adjacency matrix */ -void print_adj_matrix(int adj[][N + 1]) { - // Traverse the adj[][] - for (int i = 1; i < N + 1; i++) { - for (int j = 1; j < N + 1; j++) { - // Print the value at adj[i][j] - printf("%d ", adj[i][j]); - } - printf("\n"); - } -} - -/** Main Function */ -int main() { - // Number of vertices - N = 5; - - // Given edges - int arr[][2] = {{1, 2}, {2, 3}, {4, 5}, {1, 5}}; - - // Number of edges - M = sizeof(arr) / sizeof(arr[0]); - - int adj[N + 1][N + 1]; - - create_adj_matrix(adj, arr); - - print_adj_matrix(adj); - - return 0; -} diff --git a/code/02-graphs-eulerian-path.c b/code/c/01-graphs_eulerian_path.c similarity index 100% rename from code/02-graphs-eulerian-path.c rename to code/c/01-graphs_eulerian_path.c diff --git a/code/02-graphs-hamiltonian-cycle.c b/code/c/01-graphs_hamiltonian_cycle.c similarity index 100% rename from code/02-graphs-hamiltonian-cycle.c rename to code/c/01-graphs_hamiltonian_cycle.c diff --git a/code/03-tree-detection.c b/code/c/02-tree_detection.c similarity index 100% rename from code/03-tree-detection.c rename to code/c/02-tree_detection.c diff --git a/code/04-subset_sum.c b/code/c/03-subset_sum.c similarity index 100% rename from code/04-subset_sum.c rename to code/c/03-subset_sum.c diff --git a/code/05-knapsack.c b/code/c/04-knapsack.c similarity index 100% rename from code/05-knapsack.c rename to code/c/04-knapsack.c diff --git a/code/06-linear_search.c b/code/c/05-linear_search.c similarity index 100% rename from code/06-linear_search.c rename to code/c/05-linear_search.c diff --git a/code/07-binary_search.c b/code/c/06-binary_search.c similarity index 100% rename from code/07-binary_search.c rename to code/c/06-binary_search.c diff --git a/code/08-bfs_search.c b/code/c/07-bfs_search.c similarity index 100% rename from code/08-bfs_search.c rename to code/c/07-bfs_search.c diff --git a/code/09-dfs_search.c b/code/c/08-dfs_search.c similarity index 100% rename from code/09-dfs_search.c rename to code/c/08-dfs_search.c diff --git a/code/10-bubble_sort.c b/code/c/09-bubble_sort.c similarity index 100% rename from code/10-bubble_sort.c rename to code/c/09-bubble_sort.c diff --git a/code/11-selection_sort.c b/code/c/10-selection_sort.c similarity index 100% rename from code/11-selection_sort.c rename to code/c/10-selection_sort.c diff --git a/code/12-insertion_sort.c b/code/c/11-insertion_sort.c similarity index 100% rename from code/12-insertion_sort.c rename to code/c/11-insertion_sort.c diff --git a/code/13-merge_sort.c b/code/c/12-merge_sort.c similarity index 100% rename from code/13-merge_sort.c rename to code/c/12-merge_sort.c diff --git a/code/14-quick_sort.c b/code/c/13-quick_sort.c similarity index 100% rename from code/14-quick_sort.c rename to code/c/13-quick_sort.c diff --git a/code/15-heap_sort.c b/code/c/14-heap_sort.c similarity index 100% rename from code/15-heap_sort.c rename to code/c/14-heap_sort.c diff --git a/code/16-counting_sort.c b/code/c/15-counting_sort.c similarity index 100% rename from code/16-counting_sort.c rename to code/c/15-counting_sort.c diff --git a/code/17-radix_sort.c b/code/c/16-radix_sort.c similarity index 100% rename from code/17-radix_sort.c rename to code/c/16-radix_sort.c diff --git a/code/18-recursion_palindrome.c b/code/c/17-recursion_palindrome.c similarity index 100% rename from code/18-recursion_palindrome.c rename to code/c/17-recursion_palindrome.c diff --git a/code/19-divide_and_conquer_power.c b/code/c/18-divide_and_conquer_power.c similarity index 100% rename from code/19-divide_and_conquer_power.c rename to code/c/18-divide_and_conquer_power.c diff --git a/code/zig/01-graphs_eulerian_path.zig b/code/zig/01-graphs_eulerian_path.zig new file mode 100644 index 0000000..b3a7d8e --- /dev/null +++ b/code/zig/01-graphs_eulerian_path.zig @@ -0,0 +1,139 @@ +const std = @import("std"); +const print = std.debug.print; + +/// Number of vertices in the graph +const N = 5; +/// Maximum length of the path +const MAX_PATH_LENGTH = 20; + +/// Find Eulerian Path in a graph +/// +/// # Parameters +/// +/// - `graph`: Adjacency matrix of the graph +fn findPath(graph: *[N][N]i32) void { + var numofadj: [N]i32 = [_]i32{0} ** N; + var stack: [N]i32 = [_]i32{0} ** N; + var path: [MAX_PATH_LENGTH]i32 = [_]i32{0} ** MAX_PATH_LENGTH; + var top: usize = 0; + var path_length: usize = 0; + + // Find out number of edges each vertex has + for (0..N) |i| { + for (0..N) |j| { + numofadj[i] += graph.*[i][j]; + } + } + + // Find out how many vertices have an odd number of edges + var startpoint: usize = 0; + var numofodd: i32 = 0; + var idx_i: usize = N; + while (idx_i > 0) : (idx_i -= 1) { + const idx = idx_i - 1; + if (@mod(numofadj[idx], 2) == 1) { + numofodd += 1; + startpoint = idx; + } + } + + // If the number of vertices with odd number of edges is greater than two, return "No Solution". + if (numofodd > 2) { + print("No Solution\n", .{}); + return; + } + + // Initialize empty stack and path; take the starting current as discussed + var cur: usize = startpoint; + + // Loop will run until there is an element in the stack or current vertex has some neighbor. + while (top > 0 or numofadj[cur] > 0) { + // If current node has no neighbors, add it to path and pop stack; set new current to the popped element + if (numofadj[cur] == 0) { + path[path_length] = @intCast(cur); + path_length += 1; + if (top > 0) { + top -= 1; + cur = @intCast(stack[top]); + } else { + break; + } + } + // If the current vertex has at least one neighbor, add the current vertex to stack, + // remove the edge between them, and set the current to its neighbor. + else { + for (0..N) |j| { + if (graph.*[cur][j] == 1) { + stack[top] = @intCast(cur); + top += 1; + graph.*[cur][j] = 0; + graph.*[j][cur] = 0; + numofadj[cur] -= 1; + numofadj[j] -= 1; + cur = j; + break; + } + } + } + } + + // Add the last vertex to the path + path[path_length] = @intCast(cur); + path_length += 1; + + // Print the path + var idx: usize = path_length; + while (idx > 0) : (idx -= 1) { + print("{d} -> ", .{path[idx - 1]}); + } + print("\n", .{}); +} + +/// Main function +pub fn main() !void { + // Test case 1: + // 0 --- 1 + // | | \ + // | 2--3 + // 4 + var graph1: [N][N]i32 = .{ + .{ 0, 1, 0, 0, 1 }, + .{ 1, 0, 1, 1, 0 }, + .{ 0, 1, 0, 1, 0 }, + .{ 0, 1, 1, 0, 0 }, + .{ 1, 0, 0, 0, 0 }, + }; + + print("Test Case 1:\n", .{}); + findPath(&graph1); + + // Test case 2: + // 0 -- 1 -- 2 + // /| / \ | \ + // 3--4 5 + var graph2: [N][N]i32 = .{ + .{ 0, 1, 0, 1, 1 }, + .{ 1, 0, 1, 0, 1 }, + .{ 0, 1, 0, 1, 1 }, + .{ 1, 0, 1, 0, 0 }, + .{ 1, 1, 1, 0, 0 }, + }; + + print("Test Case 2:\n", .{}); + findPath(&graph2); + + // Test case 3: + // 0 --- 1 + // /|\ |\ + // 2 4---5 3 + var graph3: [N][N]i32 = .{ + .{ 0, 1, 1, 0, 1 }, + .{ 1, 0, 0, 1, 1 }, + .{ 1, 0, 0, 1, 0 }, + .{ 0, 1, 1, 0, 1 }, + .{ 1, 1, 0, 1, 0 }, + }; + + print("Test Case 3:\n", .{}); + findPath(&graph3); +} diff --git a/code/zig/01-graphs_hamiltonian_cycle.zig b/code/zig/01-graphs_hamiltonian_cycle.zig new file mode 100644 index 0000000..ae7e3ff --- /dev/null +++ b/code/zig/01-graphs_hamiltonian_cycle.zig @@ -0,0 +1,120 @@ +const std = @import("std"); +const print = std.debug.print; + +/// Number of vertices in the graph +const N = 5; + +/// A utility function to print the solution +fn printSolution(path: [N]i32) void { + print("Solution Exists: ", .{}); + for (0..N) |i| { + print("{d} ", .{path[i]}); + } + // Print the first vertex again to show the complete cycle + print("{d}\n\n", .{path[0]}); +} + +/// A utility function to check if the vertex `v` can be added at index `pos` +/// in the Hamiltonian cycle constructed so far (stored in `path`) +fn isSafe(v: i32, graph: *[N][N]i32, path: [N]i32, pos: usize) bool { + // Check if this vertex is an adjacent vertex of the previously added vertex. + if (graph.*[@intCast(path[pos - 1])][@intCast(v)] == 0) return false; + + // Check if the vertex has already been included. + for (0..pos) |i| { + if (path[i] == v) return false; + } + + return true; +} + +/// A recursive utility function to solve the Hamiltonian cycle problem +fn hamiltonianCycleUtil(graph: *[N][N]i32, path: *[N]i32, pos: usize) bool { + // Base case: If all vertices are included in the Hamiltonian cycle + if (pos == N) { + // And if there is an edge from the last included vertex to the first vertex + if (graph.*[@intCast(path[pos - 1])][@intCast(path[0])] == 1) { + return true; + } else { + return false; + } + } + + // Try different vertices as the next candidate in the Hamiltonian cycle. + // We don't try for 0 as we included 0 as the starting point in hamiltonianCycle() + for (1..N) |v| { + const v_int: i32 = @intCast(v); + // Check if this vertex can be added to the Hamiltonian cycle + if (isSafe(v_int, graph, path.*, pos)) { + path.*[pos] = v_int; + + // Recur to construct the rest of the path + if (hamiltonianCycleUtil(graph, path, pos + 1)) + return true; + + // If adding vertex `v` doesn't lead to a solution, then remove it + path.*[pos] = -1; + } + } + + // If no vertex can be added to the Hamiltonian cycle constructed so far, + // then return false + return false; +} + +/// This function solves the Hamiltonian cycle problem using backtracking. +/// It mainly uses `hamiltonianCycleUtil()` to solve the problem. +/// It returns false if there is no Hamiltonian cycle possible, +/// otherwise returns true and prints the path. +fn hamiltonianCycle(graph: *[N][N]i32) bool { + var path: [N]i32 = [_]i32{-1} ** N; + + // Let us put vertex 0 as the first vertex in the path. + // If there is a Hamiltonian cycle, + // then the path can be started from any point + // of the cycle as the graph is undirected + path[0] = 0; + if (!hamiltonianCycleUtil(graph, &path, 1)) { + print("Solution does not exist\n\n", .{}); + return false; + } + + printSolution(path); + return true; +} + +/// Main function +pub fn main() !void { + // Test case 1: + // (0)--(1)--(2) + // | / \ | + // | / \ | + // | / \ | + // (3)-------(4) + var graph1: [N][N]i32 = .{ + .{ 0, 1, 0, 1, 0 }, + .{ 1, 0, 1, 1, 1 }, + .{ 0, 1, 0, 0, 1 }, + .{ 1, 1, 0, 0, 1 }, + .{ 0, 1, 1, 1, 0 }, + }; + + _ = hamiltonianCycle(&graph1); + + // Test case 2: + // (0)--(1)--(2) + // | / \ | + // | / \ | + // | / \ | + // (3) (4) + var graph2: [N][N]i32 = .{ + .{ 0, 1, 0, 1, 0 }, + .{ 1, 0, 1, 1, 1 }, + .{ 0, 1, 0, 0, 1 }, + .{ 1, 1, 0, 0, 0 }, + .{ 0, 1, 1, 0, 0 }, + }; + + print("Test Case 2:\n", .{}); + _ = hamiltonianCycle(&graph2); +} diff --git a/code/zig/02-tree_detection.zig b/code/zig/02-tree_detection.zig new file mode 100644 index 0000000..04823c2 --- /dev/null +++ b/code/zig/02-tree_detection.zig @@ -0,0 +1,106 @@ +const std = @import("std"); +const print = @import("std").debug.print; + +/// Number of vertices in the graph +const N = 5; + +/// Search for a cycle in a graph +/// +/// # Parameters +/// +/// - `graph`: adjacency matrix of the graph +/// - `v`: current vertex +/// - `visited`: array of visited vertices +/// - `parent`: parent of the current vertex +/// +/// # Returns +/// +/// `true` if a cycle is found, `false` otherwise +fn searchCycle(graph: *[N][N]i32, v: usize, visited: *[N]bool, parent: isize) bool { + visited[v] = true; + for (0..N) |i| { + if (graph.*[v][i] != 0) { + if (!visited[i]) { + if (searchCycle(graph, i, visited, @intCast(v))) return true; + } else if (@as(isize, @intCast(i)) != parent) { + // Cycle detected + return true; + } + } + } + return false; +} + +/// Check if all vertices have been visited +/// +/// # Parameters +/// +/// - `visited`: array of visited vertices +/// +/// # Returns +/// +/// `true` if all vertices have been visited, `false` otherwise +fn allVisited(visited: [N]bool) bool { + var i: usize = 0; + // Find first unvisited vertex + while (i < N and visited[i]) : (i += 1) {} + // Return true if no unvisited vertices found, false otherwise + return i == N; +} + +/// Check if a graph is a tree +/// +/// # Parameters +/// +/// - `graph`: adjacency matrix of the graph +/// +/// # Returns +/// +/// `true` if the graph is a tree, `false` otherwise +fn isTree(graph: *[N][N]i32) bool { + var visited: [N]bool = [_]bool{false} ** N; + + // Perform depth-first search from vertex 0 to check if all vertices + // are reachable (connected) + const has_cycle = searchCycle(graph, 0, &visited, -1); + if (has_cycle) return false; + + // Check if all vertices have been visited + if (!allVisited(visited)) return false; + + // Return true if no cycle is found and all vertices are connected + return true; +} + +/// Main function +pub fn main() !void { + // Tree example + // (0)--(1)--(2) + // | + // | + // | + // (3)-------(4) + var graph1: [N][N]i32 = .{ + .{ 0, 1, 0, 1, 0 }, + .{ 1, 0, 1, 0, 0 }, + .{ 0, 1, 0, 0, 0 }, + .{ 1, 0, 0, 0, 1 }, + .{ 0, 0, 0, 1, 0 }, + }; + print("Test Case 1: Is Tree? {}\n", .{isTree(&graph1)}); + + // Non-tree example (has at least one cycle) + // (0)--(1)--(2) + // | / \ | + // | / \ | + // | / \ | + // (3)-------(4) + var graph2: [N][N]i32 = .{ + .{ 0, 1, 0, 1, 0 }, + .{ 1, 0, 1, 1, 1 }, + .{ 0, 1, 0, 0, 1 }, + .{ 1, 1, 0, 0, 1 }, + .{ 0, 1, 1, 1, 0 }, + }; + print("Test Case 1: Is Tree? {}\n", .{isTree(&graph2)}); +} diff --git a/code/zig/03-subset_sum.zig b/code/zig/03-subset_sum.zig new file mode 100644 index 0000000..f2d9131 --- /dev/null +++ b/code/zig/03-subset_sum.zig @@ -0,0 +1,44 @@ +const std = @import("std"); +const print = std.debug.print; + +/// Function that verifies if there is a subset that sums to the target +/// +/// # Parameters +/// +/// - `set`: The set of numbers +/// - `target`: The target sum +/// +/// # Returns +/// +/// `true` if there is a subset that sums to the target, `false` otherwise +fn subsetSum(set: []const i32, target: i32) bool { + const n: usize = set.len; + const num_subsets = @as(u8, 1) <<| n; + // Iterate through all possible subsets + for (0..num_subsets) |i| { + var sum: i32 = 0; + for (0..n) |j| { + // Verify if the j-th element is in the subset + if ((i & (@as(u8, 1) <<| j)) != 0) { + sum += set[j]; + } + } + // If the subset sums to the target, return true + if (sum == target) { + return true; + } + } + return false; +} + +/// Main function +pub fn main() !void { + const set = [_]i32{ 1, 2, 3, 4, 5 }; + const target: i32 = 9; + + if (subsetSum(set[0..], target)) { + print("Yes, there is a subset that sums to {d}\n", .{target}); + } else { + print("No, there isn't a subset that sums to {d}\n", .{target}); + } +} diff --git a/code/zig/04-knapsack.zig b/code/zig/04-knapsack.zig new file mode 100644 index 0000000..dae56cc --- /dev/null +++ b/code/zig/04-knapsack.zig @@ -0,0 +1,62 @@ +const std = @import("std"); +const print = std.debug.print; + +/// Function to find the maximum of two integers. +/// +/// # Parameters +/// +/// - `a`: The first integer. +/// - `b`: The second integer. +/// +/// # Returns +/// +/// The maximum of the two integers. +fn max(a: i32, b: i32) i32 { + return if (a > b) a else b; +} + +/// Recursive function to solve the 0/1 Knapsack problem using brute force. +/// +/// # Parameters +/// +/// - `W`: The remaining capacity of the knapsack. +/// - `weights`: Array containing the weights of the items. +/// - `values`: Array containing the values of the items. +/// - `n`: The number of items remaining to consider. +/// +/// # Returns +/// +/// The maximum value that can be obtained with the given capacity. +fn knapsack(W: i32, weights: []const i32, values: []const i32, n: usize) i32 { + // Base case: no items left or no capacity left + if (n == 0 or W == 0) { + return 0; + } + + // If the weight of the nth item is more than the remaining capacity, it cannot be included + if (weights[n - 1] > W) { + return knapsack(W, weights, values, n - 1); + } else { + // Return the maximum of two cases: + // 1. nth item included + // 2. nth item not included + return max( + values[n - 1] + knapsack(W - weights[n - 1], weights, values, n - 1), + knapsack(W, weights, values, n - 1), + ); + } +} + +/// Main function to demonstrate the brute force solution for the knapsack problem. +pub fn main() !void { + const values = [_]i32{ 60, 100, 120 }; // Values of the items + const weights = [_]i32{ 10, 20, 30 }; // Weights of the items + const W: i32 = 50; // Maximum capacity of the knapsack + const n: usize = values.len; // Number of items + + // Calculate the maximum value that can be carried in the knapsack + const max_value = knapsack(W, weights[0..], values[0..], n); + + // Print the result + print("Maximum value that can be carried: {d}\n", .{max_value}); +} diff --git a/code/zig/05-linear_search.zig b/code/zig/05-linear_search.zig new file mode 100644 index 0000000..383282b --- /dev/null +++ b/code/zig/05-linear_search.zig @@ -0,0 +1,64 @@ +const std = @import("std"); +const print = std.debug.print; +const time = std.time; +const Instant = time.Instant; +const allocator = std.heap.page_allocator; + +/// Function to perform linear search on an array. +/// +/// # Parameters +/// +/// - `arr`: The array to search in. +/// - `x`: The target value to search for. +/// +/// # Returns +/// +/// The index of the target if found, otherwise `null`. +fn linearSearch(arr: []const usize, x: usize) ?usize { + for (0.., arr) |index, item| { + if (item == x) return index; // Element found at position index + } + return null; // Element not found +} + +/// Main function to demonstrate linear search and measure execution time. +pub fn main() !void { + const N_values = [_]usize{ 1_000_000, 2_000_000, 4_000_000, 8_000_000, 16_000_000 }; + + for (N_values) |N| { + // Allocate memory for the array + var arr = try allocator.alloc(usize, N); + defer allocator.free(arr); + + // Fill the array with sequential elements + for (0..N) |i| { + arr[i] = i; + } + + // Set the target value to be at the end of the array + // (worst case for linear search) + const target: usize = N - 1; + + // Measure the start time + const start = try Instant.now(); + + // Perform linear search + const result = linearSearch(arr[0..], target); + + // Measure the end time + const end = try Instant.now(); + + // Calculate the elapsed time in seconds + const elapsed: f64 = @floatFromInt(end.since(start)); + const time_spent = elapsed / time.ns_per_s; + + // Print the result and time + print("Linear Search with N = {d}\n", .{N}); + if (result) |found| { + print("Target found at index {d}\n", .{found}); + } else { + print("Target not found\n", .{}); + } + print("Time taken: {d} seconds\n\n", .{time_spent}); + } +} diff --git a/code/zig/06-binary_search.zig b/code/zig/06-binary_search.zig new file mode 100644 index 0000000..11ad316 --- /dev/null +++ b/code/zig/06-binary_search.zig @@ -0,0 +1,76 @@ +const std = @import("std"); +const print = std.debug.print; +const time = std.time; +const Instant = time.Instant; +const allocator = std.heap.page_allocator; + +/// Function to perform binary search on a sorted array. +/// +/// # Parameters +/// +/// - `arr`: The sorted array to search in. +/// - `x`: The target value to search for. +/// +/// # Returns +/// +/// The index of the target if found, otherwise `null`. +fn binarySearch(arr: []const usize, x: usize) ?usize { + var left: usize = 0; + var right: usize = arr.len - 1; + + while (left <= right) { + const mid: usize = left + @divFloor(right - left, 2); + if (arr[mid] == x) { + // Element found + return mid; + } + if (arr[mid] < x) { + left = mid + 1; + } else { + right = mid - 1; + } + } + return null; // Element not found +} + +/// Main function to demonstrate binary search and measure execution time. +pub fn main() !void { + const N_values = [_]usize{ 1_000_000, 2_000_000, 4_000_000, 8_000_000, 16_000_000 }; + + for (N_values) |N| { + // Allocate memory for the array + var arr = try allocator.alloc(usize, N); + defer allocator.free(arr); + + // Fill the array with sequential elements + for (0..N) |i| { + arr[i] = i; + } + + // Set the target value to be at the end of the array + // (worst case for binary search) + const target: usize = N - 1; + + // Measure the start time + const start = try Instant.now(); + + // Perform binary search + const result = binarySearch(arr[0..], target); + + // Measure the end time + const end = try Instant.now(); + + // Calculate the elapsed time in seconds + const elapsed: f64 = @floatFromInt(end.since(start)); + const time_spent = elapsed / time.ns_per_s; + + // Print the result and time + print("Binary Search with N = {d}\n", .{N}); + if (result) |found| { + print("Target found at index {d}\n", .{found}); + } else { + print("Target not found\n", .{}); + } + print("Time taken: {d} seconds\n\n", .{time_spent}); + } +} diff --git a/code/zig/07-bfs_search.zig b/code/zig/07-bfs_search.zig new file mode 100644 index 0000000..70fd240 --- /dev/null +++ b/code/zig/07-bfs_search.zig @@ -0,0 +1,191 @@ +const std = @import("std"); +const print = std.debug.print; +const time = std.time; +const Instant = time.Instant; +const allocator = std.heap.page_allocator; +const Allocator = std.mem.Allocator; + +const MAX_NODES: usize = 50_000; // Adjust as needed + +/// Node structure for adjacency list +const Node = struct { + vertex: usize, + next: ?*Node, +}; + +/// Graph structure +const Graph = struct { + numVertices: usize, + adjLists: []?*Node, // Array of optional pointers to Nodes + visited: []bool, // Array of visited flags + + /// Deinitialize the graph by freeing allocated memory + fn deinit(self: *Graph, alloc: Allocator) void { + // Free all nodes in adjacency lists + for (self.adjLists) |nodePtr| { + var node = nodePtr; + while (node) |currentNode| { + const nextNode = currentNode.next; + alloc.destroy(currentNode); + node = nextNode; + } + } + alloc.free(self.adjLists); + alloc.free(self.visited); + } +}; + +/// Queue structure for BFS +const Queue = struct { + items: [MAX_NODES]usize, // Fixed-size array for simplicity + front: ?usize, + rear: ?usize, + + /// Initialize a new queue + pub fn init() Queue { + return Queue{ + .items = undefined, // Items will be uninitialized initially + .front = null, + .rear = null, + }; + } + + /// Check if the queue is empty + fn isEmpty(self: *Queue) bool { + return self.rear == null; + } + + /// Enqueue function + fn enqueue(self: *Queue, value: usize) !void { + if (self.rear == null) { + self.rear = 0; + } else { + if (self.rear.? == MAX_NODES - 1) return QueueError.QueueFull; + } + if (self.front == null) self.front = 0; + self.rear.? += 1; + self.items[self.rear.?] = value; + } + + /// Dequeue function + fn dequeue(self: *Queue) !usize { + if (self.isEmpty()) { + return error.QueueEmpty; + } else { + const item = self.items[self.front.?]; + self.front.? += 1; + if (self.front.? > self.rear.?) { + // Reset the queue + self.front = null; + self.rear = null; + } + return item; + } + } +}; + +/// Error definitions for Queue operations +const QueueError = error{ + QueueFull, + QueueEmpty, +}; + +/// Function to create a node +fn createNode(alloc: Allocator, v: usize) !*Node { + const newNode = try alloc.create(Node); + newNode.* = Node{ + .vertex = v, + .next = null, + }; + return newNode; +} + +/// Function to create a graph +fn createGraph(alloc: Allocator, vertices: usize) !*Graph { + const graph = try alloc.create(Graph); + graph.numVertices = vertices; + + graph.adjLists = try alloc.alloc(?*Node, vertices); + graph.visited = try alloc.alloc(bool, vertices); + + for (0..vertices) |i| { + graph.adjLists[i] = null; + graph.visited[i] = false; + } + + return graph; +} + +/// Function to add edge to an undirected graph +fn addEdge(alloc: Allocator, graph: *Graph, src: usize, dest: usize) !void { + // Add edge from src to dest + var newNode = try createNode(alloc, dest); + newNode.next = graph.adjLists[src]; + graph.adjLists[src] = newNode; + + // Since the graph is undirected, add edge from dest to src also + newNode = try createNode(alloc, src); + newNode.next = graph.adjLists[dest]; + graph.adjLists[dest] = newNode; +} + +/// BFS algorithm +fn bfs(graph: *Graph, startVertex: usize) !void { + var q = Queue.init(); + + graph.visited[startVertex] = true; + try q.enqueue(startVertex); + + while (!q.isEmpty()) { + const currentVertex = try q.dequeue(); + + var temp = graph.adjLists[currentVertex]; + while (temp) |node| { + const adjVertex = node.vertex; + + if (!graph.visited[adjVertex]) { + graph.visited[adjVertex] = true; + try q.enqueue(adjVertex); + } + temp = node.next; + } + } +} + +/// Main function to demonstrate BFS and measure execution time. +pub fn main() !void { + const N_values = [_]usize{ 1_000, 5_000, 10_000, 20_000, 50_000 }; + + for (N_values) |N| { + if (N > MAX_NODES) { + print("N exceeds MAX_NODES limit.\n", .{}); + continue; + } + + const graph = try createGraph(allocator, N); + defer allocator.destroy(graph); + defer graph.deinit(allocator); + + // Build a connected graph (e.g., a chain) + for (0..N - 1) |i| { + try addEdge(allocator, graph, i, i + 1); + } + + // Measure start time + const start = try Instant.now(); + + // Perform BFS starting from vertex 0 + try bfs(graph, 0); + + // Measure end time + const end = try Instant.now(); + + // Calculate the elapsed time in seconds + const elapsed: f64 = @floatFromInt(end.since(start)); + const time_spent = elapsed / time.ns_per_s; + + // Print the result + print("BFS with N = {d}\n", .{N}); + print("Time taken: {d} seconds\n\n", .{time_spent}); + } +} diff --git a/code/zig/08-dfs_search.zig b/code/zig/08-dfs_search.zig new file mode 100644 index 0000000..fdb5502 --- /dev/null +++ b/code/zig/08-dfs_search.zig @@ -0,0 +1,128 @@ +const std = @import("std"); +const print = std.debug.print; +const time = std.time; +const Instant = time.Instant; +const allocator = std.heap.page_allocator; +const Allocator = std.mem.Allocator; + +const MAX_NODES: usize = 50_000; // Adjust as needed + +/// Node structure for adjacency list +const Node = struct { + vertex: usize, + next: ?*Node, +}; + +/// Graph structure +const Graph = struct { + numVertices: usize, + adjLists: []?*Node, // Array of optional pointers to Nodes + visited: []bool, // Array of visited flags + + /// Deinitialize the graph by freeing allocated memory + fn deinit(self: *Graph, alloc: Allocator) void { + // Free all nodes in adjacency lists + for (self.adjLists) |nodePtr| { + var node = nodePtr; + while (node) |currentNode| { + const nextNode = currentNode.next; + alloc.destroy(currentNode); + node = nextNode; + } + } + alloc.free(self.adjLists); + alloc.free(self.visited); + } +}; + +/// Function to create a node +fn createNode(alloc: Allocator, v: usize) !*Node { + const newNode = try alloc.create(Node); + newNode.* = Node{ + .vertex = v, + .next = null, + }; + return newNode; +} + +/// Function to create a graph +fn createGraph(alloc: Allocator, vertices: usize) !*Graph { + const graph = try alloc.create(Graph); + graph.numVertices = vertices; + + graph.adjLists = try alloc.alloc(?*Node, vertices); + graph.visited = try alloc.alloc(bool, vertices); + + for (0..vertices) |i| { + graph.adjLists[i] = null; + graph.visited[i] = false; + } + + return graph; +} + +/// Function to add edge to an undirected graph +fn addEdge(alloc: Allocator, graph: *Graph, src: usize, dest: usize) !void { + // Add edge from src to dest + var newNode = try createNode(alloc, dest); + newNode.next = graph.adjLists[src]; + graph.adjLists[src] = newNode; + + // Since the graph is undirected, add edge from dest to src also + newNode = try createNode(alloc, src); + newNode.next = graph.adjLists[dest]; + graph.adjLists[dest] = newNode; +} + +/// DFS algorithm +fn dfs(graph: *Graph, vertex: usize) void { + graph.visited[vertex] = true; + + var temp = graph.adjLists[vertex]; + while (temp) |node| { + const adjVertex = node.vertex; + + if (!graph.visited[adjVertex]) { + dfs(graph, adjVertex); + } + temp = node.next; + } +} + +/// Main function to demonstrate DFS and measure execution time. +pub fn main() !void { + const N_values = [_]usize{ 1_000, 5_000, 10_000, 20_000, 50_000 }; + + for (N_values) |N| { + if (N > MAX_NODES) { + print("N exceeds MAX_NODES limit.\n", .{}); + continue; + } + + const graph = try createGraph(allocator, N); + defer allocator.destroy(graph); + defer graph.deinit(allocator); + + // Build a connected graph (e.g., a chain) + for (0..N - 1) |i| { + try addEdge(allocator, graph, i, i + 1); + } + + // Measure start time + const start = try Instant.now(); + + // Perform DFS starting from vertex 0 + dfs(graph, 0); + + // Measure end time + const end = try Instant.now(); + + // Calculate the elapsed time in seconds + const elapsed: f64 = @floatFromInt(end.since(start)); + const time_spent = elapsed / time.ns_per_s; + + // Print the result + print("DFS with N = {d}\n", .{N}); + print("Time taken: {d} seconds\n\n", .{time_spent}); + } +} diff --git a/code/zig/09-bubble_sort.zig b/code/zig/09-bubble_sort.zig new file mode 100644 index 0000000..d15b3c0 --- /dev/null +++ b/code/zig/09-bubble_sort.zig @@ -0,0 +1,85 @@ +const std = @import("std"); +const print = std.debug.print; +const time = std.time; +const Instant = time.Instant; +const allocator = std.heap.page_allocator; +const rand = std.rand; +const mem = std.mem; + +/// Function to perform Bubble Sort on an array. +/// +/// # Parameters +/// +/// - `arr`: The array to sort. +fn bubbleSort(arr: []usize) void { + const n = arr.len; + for (0..n - 1) |i| { + for (0..(n - i - 1)) |j| { + if (arr[j] > arr[j + 1]) { + // Swap arr[j] and arr[j + 1] + mem.swap(usize, &arr[j], &arr[j + 1]); + } + } + } +} + +/// Function to verify if an array is sorted. +/// +/// # Parameters +/// +/// - `arr`: The array to check. +fn isSorted(arr: []usize) bool { + const n = arr.len; + for (0..n - 1) |i| { + if (arr[i] > arr[i + 1]) { + return false; + } + } + return true; +} + +/// Main function to demonstrate Bubble Sort and measure execution time. +pub fn main() !void { + const N_values = [_]usize{ 10_000, 20_000, 30_000, 40_000 }; + + // Seed the random number generator + const seed: u64 = @intCast(time.nanoTimestamp()); + var rng = rand.DefaultPrng.init(seed); // Xoshiro256 is good enough + + for (N_values) |N| { + // Allocate memory for the array + var arr = try allocator.alloc(usize, N); + defer allocator.free(arr); + + // Fill the array with random integers + for (0..N) |i| { + const random_value: usize = rng.next() % N; + arr[i] = random_value; + } + + // Measure the start time + const start = try Instant.now(); + + // Perform Bubble Sort + bubbleSort(arr); + + // Measure the end time + const end = try Instant.now(); + + // Calculate the elapsed time in seconds + const elapsed: f64 = @floatFromInt(end.since(start)); + const time_spent = elapsed / time.ns_per_s; + + // Verify that the array is sorted + const is_sorted = isSorted(arr); + + // Print the result and time + print("Bubble Sort with N = {d}\n", .{N}); + if (is_sorted) { + print("Array is sorted.\n", .{}); + } else { + print("Array is NOT sorted.\n", .{}); + } + print("Time taken: {d} seconds\n\n", .{time_spent}); + } +} diff --git a/code/zig/10-selection_sort.zig b/code/zig/10-selection_sort.zig new file mode 100644 index 0000000..5e0b6e5 --- /dev/null +++ b/code/zig/10-selection_sort.zig @@ -0,0 +1,90 @@ +const std = @import("std"); +const print = std.debug.print; +const time = std.time; +const Instant = time.Instant; +const allocator = std.heap.page_allocator; +const rand = std.rand; +const mem = std.mem; + +/// Function to perform Selection Sort on an array. +/// +/// # Parameters +/// +/// - `arr`: The array to sort. +fn selectionSort(arr: []usize) void { + const n = arr.len; + for (0..n - 1) |i| { + var min_index = i; + + // Find the minimum element in the unsorted portion + for (i + 1..n) |j| { + if (arr[j] < arr[min_index]) { + min_index = j; + } + } + + // Swap the found minimum element with the first unsorted element + mem.swap(usize, &arr[min_index], &arr[i]); + } +} + +/// Function to verify if an array is sorted. +/// +/// # Parameters +/// +/// - `arr`: The array to check. +fn isSorted(arr: []usize) bool { + const n = arr.len; + for (0..n - 1) |i| { + if (arr[i] > arr[i + 1]) { + return false; + } + } + return true; +} + +/// Main function to demonstrate Selection Sort and measure execution time. +pub fn main() !void { + const N_values = [_]usize{ 10_000, 20_000, 30_000, 40_000 }; + + // Seed the random number generator + const seed: u64 = @intCast(time.nanoTimestamp()); + var rng = rand.DefaultPrng.init(seed); // Xoshiro256 is good enough + + for (N_values) |N| { + // Allocate memory for the array + var arr = try allocator.alloc(usize, N); + defer allocator.free(arr); + + // Fill the array with random integers + for (0..N) |i| { + const random_value: usize = rng.next() % N; + arr[i] = random_value; + } + + // Measure the start time + const start = try Instant.now(); + + // Perform Selection Sort + selectionSort(arr); + + // Measure the end time + const end = try Instant.now(); + + // Calculate the elapsed time in seconds + const elapsed: f64 = @floatFromInt(end.since(start)); + const time_spent = elapsed / time.ns_per_s; + + // Verify that the array is sorted + const is_sorted = isSorted(arr); + + // Print the result and time + print("Selection Sort with N = {d}\n", .{N}); + if (is_sorted) { + print("Array is sorted.\n", .{}); + } else { + print("Array is NOT sorted.\n", .{}); + } + print("Time taken: {d} seconds\n\n", .{time_spent}); + } +} diff --git a/code/zig/11-insertion_sort.zig b/code/zig/11-insertion_sort.zig new file mode 100644 index 0000000..64b83a2 --- /dev/null +++ b/code/zig/11-insertion_sort.zig @@ -0,0 +1,84 @@ +const std = @import("std"); +const print = std.debug.print; +const time = std.time; +const Instant = time.Instant; +const allocator = std.heap.page_allocator; +const rand = std.rand; + +/// Function to perform Insertion Sort on an array. +/// +/// # Parameters +/// +/// - `arr`: The array to sort. +fn insertionSort(arr: []usize) void { + var i: usize = 1; + while (i < arr.len) : (i += 1) { + const x = arr[i]; + var j = i; + while (j > 0 and arr[j - 1] > x) : (j -= 1) { + arr[j] = arr[j - 1]; + } + arr[j] = x; + } +} + +/// Function to verify if an array is sorted. +/// +/// # Parameters +/// +/// - `arr`: The array to check. +fn isSorted(arr: []usize) bool { + const n = arr.len; + for (0..n - 1) |i| { + if (arr[i] > arr[i + 1]) { + return false; + } + } + return true; +} + +/// Main function to demonstrate Insertion Sort and measure execution time. +pub fn main() !void { + const N_values = [_]usize{ 10_000, 20_000, 30_000, 40_000 }; + + // Seed the random number generator + const seed: u64 = @intCast(time.nanoTimestamp()); + var rng = rand.DefaultPrng.init(seed); // Xoshiro256 is good enough + + for (N_values) |N| { + // Allocate memory for the array + var arr = try allocator.alloc(usize, N); + defer allocator.free(arr); + + // Fill the array with random integers + for (0..N) |i| { + const random_value: usize = rng.next() % N; + arr[i] = random_value; + } + + // Measure the start time + const start = try Instant.now(); + + // Perform Insertion Sort + insertionSort(arr); + + // Measure the end time + const end = try Instant.now(); + + // Calculate the elapsed time in seconds + const elapsed: f64 = @floatFromInt(end.since(start)); + const time_spent = elapsed / time.ns_per_s; + + // Verify that the array is sorted + const is_sorted = isSorted(arr); + + // Print the result and time + print("Insertion Sort with N = {d}\n", .{N}); + if (is_sorted) { + print("Array is sorted.\n", .{}); + } else { + print("Array is NOT sorted.\n", .{}); + } + print("Time taken: {d} seconds\n\n", .{time_spent}); + } +} diff --git a/code/zig/12-merge_sort.zig b/code/zig/12-merge_sort.zig new file mode 100644 index 0000000..06cdae4 --- /dev/null +++ b/code/zig/12-merge_sort.zig @@ -0,0 +1,149 @@ +const std = @import("std"); +const print = std.debug.print; +const time = std.time; +const Instant = time.Instant; +const allocator = std.heap.page_allocator; +const Allocator = std.mem.Allocator; +const rand = std.rand; + +/// Function to perform Merge Sort on an array. +/// +/// # Parameters +/// +/// - `allocator`: The allocator to use for temporary arrays. +/// - `arr`: The array to sort. +/// - `left`: The starting index of the array. +/// - `right`: The ending index of the array. +fn mergeSort(alloc: Allocator, arr: []usize, left: usize, right: usize) !void { + if (left < right) { + // Same as (left + right)/2 but avoids overflow + const mid: usize = left + @divFloor(right - left, 2); + + // Sort first and second halves + try mergeSort(alloc, arr, left, mid); + try mergeSort(alloc, arr, mid + 1, right); + + try merge(alloc, arr, left, mid, right); + } +} + +/// Function to merge two subarrays of `arr`. +/// First subarray is `arr[left..mid+1]` +/// Second subarray is `arr[mid+1..right+1]` +/// +/// # Parameters +/// +/// - `allocator`: The allocator to use for temporary arrays. +/// - `arr`: The main array containing both subarrays. +/// - `left`: The starting index of the first subarray. +/// - `mid`: The ending index of the first subarray. +/// - `right`: The ending index of the second subarray. +fn merge(alloc: Allocator, arr: []usize, left: usize, mid: usize, right: usize) !void { + const n1 = mid - left + 1; + const n2 = right - mid; + + // Create temp arrays + var L = try alloc.alloc(usize, n1); + defer alloc.free(L); + var R = try alloc.alloc(usize, n2); + defer alloc.free(R); + + // Copy data to temp arrays L[] and R[] + for (0..n1) |i| L[i] = arr[left + i]; + for (0..n2) |j| R[j] = arr[mid + 1 + j]; + + // Merge the temp arrays back into arr[left..right] + var i: usize = 0; // Initial index of first subarray + var j: usize = 0; // Initial index of second subarray + var k: usize = left; // Initial index of merged subarray + + while (i < n1 and j < n2) { + if (L[i] <= R[j]) { + arr[k] = L[i]; + i += 1; + } else { + arr[k] = R[j]; + j += 1; + } + k += 1; + } + + // Copy the remaining elements of L[], if any + while (i < n1) : ({ + i += 1; + k += 1; + }) { + arr[k] = L[i]; + } + + // Copy the remaining elements of R[], if any + while (j < n2) : ({ + j += 1; + k += 1; + }) { + arr[k] = R[j]; + } +} + +/// Function to verify if an array is sorted. +/// +/// # Parameters +/// +/// - `arr`: The array to check. +fn isSorted(arr: []usize) bool { + const n = arr.len; + for (0..n - 1) |i| { + if (arr[i] > arr[i + 1]) { + return false; + } + } + return true; +} + +/// Main function to demonstrate Merge Sort and measure execution time. +pub fn main() !void { + const N_values = [_]usize{ 10_000, 20_000, 30_000, 40_000 }; + + // Seed the random number generator + const seed: u64 = @intCast(time.nanoTimestamp()); + var rng = rand.DefaultPrng.init(seed); // Xoshiro256 is good enough + + for (N_values) |N| { + // Allocate memory for the array + var arr = try allocator.alloc(usize, N); + defer allocator.free(arr); + + // Fill the array with random integers + for (0..N) |i| { + const random_value: usize = rng.next() % N; + arr[i] = random_value; + } + + // Measure the start time + const start = try Instant.now(); + + // Perform Merge Sort + const left: usize = 0; + const right = arr.len - 1; + try mergeSort(allocator, arr, left, right); + + // Measure the end time + const end = try Instant.now(); + + // Calculate the elapsed time in seconds + const elapsed: f64 = @floatFromInt(end.since(start)); + const time_spent = elapsed / time.ns_per_s; + + // Verify that the array is sorted + const is_sorted = isSorted(arr); + + // Print the result and time + print("Merge Sort with N = {d}\n", .{N}); + if (is_sorted) { + print("Array is sorted.\n", .{}); + } else { + print("Array is NOT sorted.\n", .{}); + } + print("Time taken: {d} seconds\n\n", .{time_spent}); + } +} diff --git a/code/zig/13-quick_sort.zig b/code/zig/13-quick_sort.zig new file mode 100644 index 0000000..1eaa93e --- /dev/null +++ b/code/zig/13-quick_sort.zig @@ -0,0 +1,118 @@ +const std = @import("std"); +const print = std.debug.print; +const time = std.time; +const Instant = time.Instant; +const allocator = std.heap.page_allocator; +const rand = std.rand; +const mem = std.mem; + +/// Function to perform Quick Sort on an array. +/// +/// # Parameters +/// +/// - `arr`: The array to sort. +/// - `low`: The starting index. +/// - `high`: The ending index. +fn quickSort(arr: []usize, low: usize, high: usize) void { + if (low < high) { + const pi = partition(arr, low, high); + + if (pi > 0) { + quickSort(arr, low, pi - 1); + } + quickSort(arr, pi + 1, high); + } +} + +/// Function to partition the array on the basis of pivot element. +/// +/// # Parameters +/// +/// - `arr`: The array to partition. +/// - `low`: The starting index. +/// - `high`: The ending index. +/// +/// # Returns +/// +/// The partition index. +fn partition(arr: []usize, low: usize, high: usize) usize { + const pivot = arr[high]; // Choose the last element as pivot + var i = low; + var j = low; + + while (j < high) : (j += 1) { + // If current element is smaller than or equal to pivot + if (arr[j] <= pivot) { + // Swap arr[i] and arr[j] + mem.swap(usize, &arr[i], &arr[j]); + i += 1; + } + } + // Swap arr[i] and arr[high] (or pivot) + mem.swap(usize, &arr[i], &arr[high]); + + return i; +} + +/// Function to verify if an array is sorted. +/// +/// # Parameters +/// +/// - `arr`: The array to check. +fn isSorted(arr: []usize) bool { + const n = arr.len; + for (0..n - 1) |i| { + if (arr[i] > arr[i + 1]) { + return false; + } + } + return true; +} + +/// Main function to demonstrate Quick Sort and measure execution time. +pub fn main() !void { + const N_values = [_]usize{ 10_000, 20_000, 30_000, 40_000 }; + + // Seed the random number generator + const seed: u64 = @intCast(time.nanoTimestamp()); + var rng = rand.DefaultPrng.init(seed); // Xoshiro256 is good enough + + for (N_values) |N| { + // Allocate memory for the array + var arr = try allocator.alloc(usize, N); + defer allocator.free(arr); + + // Fill the array with random integers + for (0..N) |i| { + const random_value: usize = rng.next() % N; + arr[i] = random_value; + } + + // Measure the start time + const start = try Instant.now(); + + // Perform Quick Sort + const low: usize = 0; + const high = arr.len - 1; + quickSort(arr, low, high); + + // Measure the end time + const end = try Instant.now(); + + // Calculate the elapsed time in seconds + const elapsed: f64 = @floatFromInt(end.since(start)); + const time_spent = elapsed / time.ns_per_s; + + // Verify that the array is sorted + const is_sorted = isSorted(arr); + + // Print the result and time + print("Insertion Sort with N = {d}\n", .{N}); + if (is_sorted) { + print("Array is sorted.\n", .{}); + } else { + print("Array is NOT sorted.\n", .{}); + } + print("Time taken: {d} seconds\n\n", .{time_spent}); + } +} diff --git a/code/zig/14-heap_sort.zig b/code/zig/14-heap_sort.zig new file mode 100644 index 0000000..75248d8 --- /dev/null +++ b/code/zig/14-heap_sort.zig @@ -0,0 +1,130 @@ +const std = @import("std"); +const print = std.debug.print; +const time = std.time; +const Instant = time.Instant; +const allocator = std.heap.page_allocator; +const rand = std.rand; +const mem = std.mem; + +/// Function to perform Heap Sort on an array. +/// +/// # Parameters +/// +/// - `arr`: The array to sort. +fn heapSort(arr: []usize) void { + const n = arr.len; + + if (n <= 1) { + return; + } + + // Build a maxheap (rearrange array) + var i = @divFloor(n, 2) - 1; + while (i >= 0) : (i -= 1) { + heapify(arr, n, i); + if (i == 0) break; + } + + // One by one extract elements from heap + i = n - 1; + while (i > 0) : (i -= 1) { + // Move current root to end + mem.swap(usize, &arr[0], &arr[i]); + + // Call max heapify on the reduced heap + heapify(arr, i, 0); + } +} + +/// Function to heapify a subtree rooted with node `i`, which is an index in `arr`. +/// +/// # Parameters +/// +/// - `arr`: The array to heapify. +/// - `n`: The size of the heap. +/// - `i`: The index of the root node of the subtree. +fn heapify(arr: []usize, n: usize, i: usize) void { + var largest: usize = i; // Initialize largest as root + const left: usize = 2 * i + 1; // left child index + const right: usize = 2 * i + 2; // right child index + + // If left child exists and is greater than root + if (left < n and arr[left] > arr[largest]) { + largest = left; + } + + // If right child exists and is greater than largest so far + if (right < n and arr[right] > arr[largest]) { + largest = right; + } + + // If largest is not root + if (largest != i) { + // Swap arr[i] with arr[largest] + mem.swap(usize, &arr[i], &arr[largest]); + + // Recursively heapify the affected sub-tree + heapify(arr, n, largest); + } +} + +/// Function to verify if an array is sorted. +/// +/// # Parameters +/// +/// - `arr`: The array to check. +fn isSorted(arr: []usize) bool { + const n = arr.len; + for (0..n - 1) |i| { + if (arr[i] > arr[i + 1]) { + return false; + } + } + return true; +} + +/// Main function to demonstrate Insertion Sort and measure execution time. +pub fn main() !void { + const N_values = [_]usize{ 10_000, 20_000, 30_000, 40_000 }; + + // Seed the random number generator + const seed: u64 = @intCast(time.nanoTimestamp()); + var rng = rand.DefaultPrng.init(seed); // Xoshiro256 is good enough + + for (N_values) |N| { + // Allocate memory for the array + var arr = try allocator.alloc(usize, N); + defer allocator.free(arr); + + // Fill the array with random integers + for (0..N) |i| { + const random_value: usize = rng.next() % N; + arr[i] = random_value; + } + + // Measure the start time + const start = try Instant.now(); + + // Perform Heap Sort + heapSort(arr); + + // Measure the end time + const end = try Instant.now(); + + // Calculate the elapsed time in seconds + const elapsed: f64 = @floatFromInt(end.since(start)); + const time_spent = elapsed / time.ns_per_s; + + // Verify that the array is sorted + const is_sorted = isSorted(arr); + + // Print the result and time + print("Insertion Sort with N = {d}\n", .{N}); + if (is_sorted) { + print("Array is sorted.\n", .{}); + } else { + print("Array is NOT sorted.\n", .{}); + } + print("Time taken: {d} seconds\n\n", .{time_spent}); + } +} diff --git a/code/zig/15-counting_sort.zig b/code/zig/15-counting_sort.zig new file mode 100644 index 0000000..736fb40 --- /dev/null +++ b/code/zig/15-counting_sort.zig @@ -0,0 +1,113 @@ +const std = @import("std"); +const print = std.debug.print; +const time = std.time; +const Instant = time.Instant; +const Allocator = std.mem.Allocator; +const allocator = std.heap.page_allocator; +const rand = std.rand; + +/// Function to perform Counting Sort on an array. +/// +/// # Parameters +/// +/// - `allocator`: The allocator to use for temporary arrays. +/// - `arr`: The array to sort. +/// - `max_value`: The maximum possible value in the array. +fn countingSort(alloc: Allocator, arr: []usize, max_value: usize) !void { + const n = arr.len; + + // Create count array with size max_value + 1, initialized to zero + var count = try alloc.alloc(usize, max_value + 1); + defer alloc.free(count); + @memset(count, 0); + + // Create output array + var output = try alloc.alloc(usize, n); + defer alloc.free(output); + + // Store the count of each element + for (arr) |value| { + count[value] += 1; + } + + // Change count[i] so that count[i] now contains actual position + // of this element in output array + var total: usize = 0; + for (0..max_value + 1) |i| { + const old_count = count[i]; + count[i] = total; + total += old_count; + } + + // Build the output array + for (arr) |value| { + const index = count[value]; + output[index] = value; + count[value] += 1; + } + + // Copy the output array back to arr[] + @memcpy(arr, output); +} + +/// Function to verify if an array is sorted. +/// +/// # Parameters +/// +/// - `arr`: The array to check. +fn isSorted(arr: []usize) bool { + const n = arr.len; + for (0..n - 1) |i| { + if (arr[i] > arr[i + 1]) { + return false; + } + } + return true; +} + +/// Main function to demonstrate Counting Sort and measure execution time. +pub fn main() !void { + const N_values = [_]usize{ 10_000, 20_000, 30_000, 40_000 }; + + // Seed the random number generator + const seed: u64 = @intCast(time.nanoTimestamp()); + var rng = rand.DefaultPrng.init(seed); // Xoshiro256 is good enough + + for (N_values) |N| { + // Allocate memory for the array + var arr = try allocator.alloc(usize, N); + defer allocator.free(arr); + + // Fill the array with random integers + for (0..N) |i| { + const random_value: usize = rng.next() % N; + arr[i] = random_value; + } + + // Measure the start time + const start = try Instant.now(); + + // Perform Counting Sort + const max_value: usize = 1_000_000; + try countingSort(allocator, arr, max_value); + + // Measure the end time + const end = try Instant.now(); + + // Calculate the elapsed time in seconds + const elapsed: f64 = @floatFromInt(end.since(start)); + const time_spent = elapsed / time.ns_per_s; + + // Verify that the array is sorted + const is_sorted = isSorted(arr); + + // Print the result and time + print("Insertion Sort with N = {d}\n", .{N}); + if (is_sorted) { + print("Array is sorted.\n", .{}); + } else { + print("Array is NOT sorted.\n", .{}); + } + print("Time taken: {d} seconds\n\n", .{time_spent}); + } +} diff --git a/code/zig/16-radix_sort.zig b/code/zig/16-radix_sort.zig new file mode 100644 index 0000000..b7ca58c --- /dev/null +++ b/code/zig/16-radix_sort.zig @@ -0,0 +1,149 @@ +const std = @import("std"); +const print = std.debug.print; +const time = std.time; +const Instant = time.Instant; +const Allocator = std.mem.Allocator; +const allocator = std.heap.page_allocator; +const rand = std.rand; + +/// Base of numeral system (decimal) +const BASE: u32 = 10; + +/// Function to perform Radix Sort on an array. +/// +/// # Parameters +/// +/// - `allocator`: The allocator to use for temporary arrays. +/// - `arr`: The array to sort. +fn radixSort(alloc: Allocator, arr: []usize) !void { + // Find the maximum number to know the number of digits + const max = getMax(arr); + + // Do counting sort for every digit + var exp: usize = 1; + while (max / exp > 0) : (exp *= BASE) { + try countingSortByDigit(alloc, arr, exp); + } +} + +/// Function to get the maximum value in an array. +/// +/// # Parameters +/// +/// - `arr`: The array to search. +/// +/// # Returns +/// +/// The maximum value in the array. +fn getMax(arr: []usize) usize { + var max: usize = arr[0]; + for (arr[1..]) |value| { + if (value > max) { + max = value; + } + } + return max; +} + +/// Function to perform Counting Sort based on a specific digit represented by `exp`. +/// +/// # Parameters +/// +/// - `allocator`: The allocator to use for temporary arrays. +/// - `arr`: The array to sort. +/// - `exp`: The exponent representing the digit position. +fn countingSortByDigit(alloc: Allocator, arr: []usize, exp: usize) !void { + const n = arr.len; + + // Create output array + var output = try alloc.alloc(usize, n); + defer alloc.free(output); + + // Initialize count array + var count: [BASE]usize = [_]usize{0} ** BASE; + + // Store count of occurrences in count[] + for (arr) |value| { + const digit = (value / exp) % BASE; + count[digit] += 1; + } + + // Change count[i] so that count[i] now contains actual position of this digit in output[] + for (1..BASE) |i| { + count[i] += count[i - 1]; + } + + // Build the output array + var i = n - 1; + while (i >= 0) : (i -= 1) { + const value = arr[i]; + const digit = (value / exp) % BASE; + count[digit] -= 1; + output[count[digit]] = value; + if (i == 0) break; + } + + // Copy the output array back to arr[] + @memcpy(arr, output); +} + +/// Function to verify if an array is sorted. +/// +/// # Parameters +/// +/// - `arr`: The array to check. +fn isSorted(arr: []usize) bool { + const n = arr.len; + for (0..n - 1) |i| { + if (arr[i] > arr[i + 1]) { + return false; + } + } + return true; +} + +/// Main function to demonstrate Radix Sort and measure execution time. +pub fn main() !void { + const N_values = [_]usize{ 10_000, 20_000, 30_000, 40_000 }; + + // Seed the random number generator + const seed: u64 = @intCast(time.nanoTimestamp()); + var rng = rand.DefaultPrng.init(seed); // Xoshiro256 is good enough + + for (N_values) |N| { + // Allocate memory for the array + var arr = try allocator.alloc(usize, N); + defer allocator.free(arr); + + // Fill the array with random integers + for (0..N) |i| { + const random_value: usize = rng.next() % N; + arr[i] = random_value; + } + + // Measure the start time + const start = try Instant.now(); + + // Perform Radix Sort + try radixSort(allocator, arr); + + // Measure the end time + const end = try Instant.now(); + + // Calculate the elapsed time in seconds + const elapsed: f64 = @floatFromInt(end.since(start)); + const time_spent = elapsed / time.ns_per_s; + + // Verify that the array is sorted + const is_sorted = isSorted(arr); + + // Print the result and time + print("Insertion Sort with N = {d}\n", .{N}); + if (is_sorted) { + print("Array is sorted.\n", .{}); + } else { + print("Array is NOT sorted.\n", .{}); + } + print("Time taken: {d} seconds\n\n", .{time_spent}); + } +} diff --git a/code/zig/17-recursion_palindrome.zig b/code/zig/17-recursion_palindrome.zig new file mode 100644 index 0000000..2a26971 --- /dev/null +++ b/code/zig/17-recursion_palindrome.zig @@ -0,0 +1,67 @@ +const std = @import("std"); + +/// Function to check if a string is a palindrome recursively. +/// +/// # Parameters +/// +/// - `str`: The string to check. +/// - `left`: The left index for comparison. +/// - `right`: The right index for comparison. +/// +/// # Returns +/// +/// `true` if the string is a palindrome, `false` otherwise. +fn isPalindromeRecursive(str: []const u8, left: usize, right: usize) bool { + // Base case: If left index is greater or equal to right, all characters have been checked + if (left >= right) { + return true; + } + + // If characters at current indices do not match, it's not a palindrome + if (str[left] != str[right]) { + return false; + } + + // Move towards the center + return isPalindromeRecursive(str, left + 1, right - 1); +} + +/// Helper function to check if a string is a palindrome. +/// +/// # Parameters +/// +/// - `str`: The string to check. +/// +/// # Returns +/// +/// `true` if the string is a palindrome, `false` otherwise. +fn isPalindrome(str: []const u8) bool { + if (str.len == 0) { + return true; + } + return isPalindromeRecursive(str, 0, str.len - 1); +} + +/// Main function to demonstrate the palindrome checker. +pub fn main() !void { + const test_strings = [_][]const u8{ + "radar", + "level", + "hello", + "racecar", + "madam", + "step on no pets", + "", + "abcba", + "not a palindrome", + }; + + for (test_strings) |str| { + std.debug.print("Testing: \"{s}\"\n", .{str}); + if (isPalindrome(str)) { + std.debug.print("Result: It's a palindrome.\n\n", .{}); + } else { + std.debug.print("Result: It's not a palindrome.\n\n", .{}); + } + } +} diff --git a/code/zig/18-divide_and_conquer.zig b/code/zig/18-divide_and_conquer.zig new file mode 100644 index 0000000..c1a3a19 --- /dev/null +++ b/code/zig/18-divide_and_conquer.zig @@ -0,0 +1,62 @@ +const std = @import("std"); +const print = std.debug.print; +const time = std.time; +const Instant = time.Instant; + +/// Function to calculate `x` raised to the power n using divide and conquer. +/// +/// # Parameters +/// +/// - `x`: The base value (can be negative or fractional). +/// - `n`: The exponent value (can be negative). +/// +/// # Returns +/// +/// The result of `x` raised to the power n as a `f64` (double precision). +fn power(x: f64, n: i32) f64 { + if (n == 0) { + return 1.0; + } + + var base = x; + var exponent = n; + + if (exponent < 0) { + base = 1.0 / base; + exponent = -exponent; + } + + const half = power(base, @divFloor(exponent, 2)); + + if (@mod(exponent, 2) == 0) { + return half * half; + } else { + return base * half * half; + } +} + +/// Main function to demonstrate the power function and measure execution time. +pub fn main() !void { + const x: f64 = 2.0; + const N_values = [_]i32{ 10, 20, 40, 80, 160, 320, 640 }; + + for (N_values) |n| { + // Measure start time + const start = try Instant.now(); + + // Calculate x^n + const result = power(x, n); + + // Measure the end time + const end = try Instant.now(); + + // Calculate the elapsed time in seconds + const elapsed: f64 = @floatFromInt(end.since(start)); + const time_spent = elapsed / time.ns_per_s; + + // Print the result and time + print("Calculating {d} ^ {d}\n", .{ x, n }); + print("Result: {e}\n", .{result}); + print("Time taken: {d} seconds\n\n", .{time_spent}); + } +} diff --git a/justfile b/justfile index 34311e2..35793e2 100644 --- a/justfile +++ b/justfile @@ -2,6 +2,8 @@ alias b := build alias b-pt := build-pt alias w := watch alias w-pt := watch-pt +alias c := c-compile +alias z := z-compile # List all the available commands default: @@ -27,12 +29,53 @@ watch-pt: @echo "Vigiando os slides" typst w slides/slides-pt.typ -# Compile the C code in "code/" -compile: +# Compile the C code in "code/c/" +c-compile: #!/usr/bin/env bash - echo "Compiling all *.c code to output" - rm -r ./output;mkdir ./output - for FILE in $(ls ./code/) - do - cc ./code/$FILE -o ./output/$FILE.out + set -e + echo "Compiling all *.c code in performance mode to \"output/\"" + mkdir -p ./output + rm -rf ./output/* + for FILE in $(ls ./code/c/); do + BASENAME="${FILE%.c}" + cc "./code/c/$FILE" -O3 -o "./output/$BASENAME.out" done + +# Compile and run the C code in "code/c/" +c-run: c-compile + #!/usr/bin/env bash + set -e + echo "Running all compiled C code in \"output\"" + for FILE in $(ls ./output/); do + "./output/$FILE" + done + +# Compile the Zig code in "code/zig/" +z-compile: + #!/usr/bin/env bash + set -e + echo "Compiling all *.zig code in performance mode to \"output/\"" + current_path=$(pwd) + mkdir -p ./output + rm -rf ./output/* + cd ./output + for FILE in $(ls "$current_path/code/zig/"); do + BASENAME="${FILE%.zig}" + zig build-exe "$current_path/code/zig/$FILE" -O ReleaseFast + done + +# Compile and run the Zig code in "code/zig/" +z-run: z-compile + #!/usr/bin/env bash + set -e + echo "Running all compiled Zig code in \"output\"" + for FILE in $(ls ./output/*.o); do + BASENAME="${FILE%.o}" + "$BASENAME" + done + +# Format Zig code in "code/zig/" +z-fmt: + #!/usr/bin/env bash + set -e + zig fmt ./code/zig/*.zig