From aa818945f012f79963e0800f797750e397fa0b33 Mon Sep 17 00:00:00 2001 From: khoaxuantu <68913255+khoaxuantu@users.noreply.github.com> Date: Fri, 24 May 2024 14:41:40 +0700 Subject: [PATCH] feat: Add Ruby code - chapter "Backtracking" (#1373) * [feat] add ruby code - chapter backtracking * feat: add ruby code block - chapter backtracking --- codes/ruby/chapter_backtracking/n_queens.rb | 61 +++++++++++++++++ .../chapter_backtracking/permutations_i.rb | 46 +++++++++++++ .../chapter_backtracking/permutations_ii.rb | 48 +++++++++++++ .../preorder_traversal_i_compact.rb | 33 +++++++++ .../preorder_traversal_ii_compact.rb | 41 +++++++++++ .../preorder_traversal_iii_compact.rb | 42 ++++++++++++ .../preorder_traversal_iii_template.rb | 68 +++++++++++++++++++ .../ruby/chapter_backtracking/subset_sum_i.rb | 47 +++++++++++++ .../subset_sum_i_naive.rb | 46 +++++++++++++ .../chapter_backtracking/subset_sum_ii.rb | 51 ++++++++++++++ .../backtracking_algorithm.md | 20 ++++++ 11 files changed, 503 insertions(+) create mode 100644 codes/ruby/chapter_backtracking/n_queens.rb create mode 100644 codes/ruby/chapter_backtracking/permutations_i.rb create mode 100644 codes/ruby/chapter_backtracking/permutations_ii.rb create mode 100644 codes/ruby/chapter_backtracking/preorder_traversal_i_compact.rb create mode 100644 codes/ruby/chapter_backtracking/preorder_traversal_ii_compact.rb create mode 100644 codes/ruby/chapter_backtracking/preorder_traversal_iii_compact.rb create mode 100644 codes/ruby/chapter_backtracking/preorder_traversal_iii_template.rb create mode 100644 codes/ruby/chapter_backtracking/subset_sum_i.rb create mode 100644 codes/ruby/chapter_backtracking/subset_sum_i_naive.rb create mode 100644 codes/ruby/chapter_backtracking/subset_sum_ii.rb diff --git a/codes/ruby/chapter_backtracking/n_queens.rb b/codes/ruby/chapter_backtracking/n_queens.rb new file mode 100644 index 000000000..b782f168e --- /dev/null +++ b/codes/ruby/chapter_backtracking/n_queens.rb @@ -0,0 +1,61 @@ +=begin +File: n_queens.rb +Created Time: 2024-05-21 +Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) +=end + +### 回溯算法:n 皇后 ### +def backtrack(row, n, state, res, cols, diags1, diags2) + # 当放置完所有行时,记录解 + if row == n + res << state.map { |row| row.dup } + return + end + + # 遍历所有列 + for col in 0...n + # 计算该格子对应的主对角线和次对角线 + diag1 = row - col + n - 1 + diag2 = row + col + # 剪枝:不允许该格子所在列、主对角线、次对角线上存在皇后 + if !cols[col] && !diags1[diag1] && !diags2[diag2] + # 尝试:将皇后放置在该格子 + state[row][col] = "Q" + cols[col] = diags1[diag1] = diags2[diag2] = true + # 放置下一行 + backtrack(row + 1, n, state, res, cols, diags1, diags2) + # 回退:将该格子恢复为空位 + state[row][col] = "#" + cols[col] = diags1[diag1] = diags2[diag2] = false + end + end +end + +### 求解 n 皇后 ### +def n_queens(n) + # 初始化 n*n 大小的棋盘,其中 'Q' 代表皇后,'#' 代表空位 + state = Array.new(n) { Array.new(n, "#") } + cols = Array.new(n, false) # 记录列是否有皇后 + diags1 = Array.new(2 * n - 1, false) # 记录主对角线上是否有皇后 + diags2 = Array.new(2 * n - 1, false) # 记录次对角线上是否有皇后 + res = [] + backtrack(0, n, state, res, cols, diags1, diags2) + + res +end + +### Driver Code ### +if __FILE__ == $0 + n = 4 + res = n_queens(n) + + puts "输入棋盘长宽为 #{n}" + puts "皇后放置方案共有 #{res.length} 种" + + for state in res + puts "--------------------" + for row in state + p row + end + end +end diff --git a/codes/ruby/chapter_backtracking/permutations_i.rb b/codes/ruby/chapter_backtracking/permutations_i.rb new file mode 100644 index 000000000..e7866ddb9 --- /dev/null +++ b/codes/ruby/chapter_backtracking/permutations_i.rb @@ -0,0 +1,46 @@ +=begin +File: permutations_i.rb +Created Time: 2024-05-22 +Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) +=end + +### 回溯算法:全排列 I ### +def backtrack(state, choices, selected, res) + # 当状态长度等于元素数量时,记录解 + if state.length == choices.length + res << state.dup + return + end + + # 遍历所有选择 + choices.each_with_index do |choice, i| + # 剪枝:不允许重复选择元素 + unless selected[i] + # 尝试:做出选择,更新状态 + selected[i] = true + state << choice + # 进行下一轮选择 + backtrack(state, choices, selected, res) + # 回退:撤销选择,恢复到之前的状态 + selected[i] = false + state.pop + end + end +end + +### 全排列 I ### +def permutations_i(nums) + res = [] + backtrack([], nums, Array.new(nums.length, false), res) + res +end + +### Driver Code ### +if __FILE__ == $0 + nums = [1, 2, 3] + + res = permutations_i(nums) + + puts "输入数组 nums = #{nums}" + puts "所有排列 res = #{res}" +end diff --git a/codes/ruby/chapter_backtracking/permutations_ii.rb b/codes/ruby/chapter_backtracking/permutations_ii.rb new file mode 100644 index 000000000..ee3e84cd9 --- /dev/null +++ b/codes/ruby/chapter_backtracking/permutations_ii.rb @@ -0,0 +1,48 @@ +=begin +File: permutations_ii.rb +Created Time: 2024-05-22 +Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) +=end + +### 回溯算法:全排列 II ### +def backtrack(state, choices, selected, res) + # 当状态长度等于元素数量时,记录解 + if state.length == choices.length + res << state.dup + return + end + + # 遍历所有选择 + duplicated = Set.new + choices.each_with_index do |choice, i| + # 剪枝:不允许重复选择元素 且 不允许重复选择相等元素 + if !selected[i] && !duplicated.include?(choice) + # 尝试:做出选择,更新状态 + duplicated.add(choice) + selected[i] = true + state << choice + # 进行下一轮选择 + backtrack(state, choices, selected, res) + # 回退:撤销选择,恢复到之前的状态 + selected[i] = false + state.pop + end + end +end + +### 全排列 II ### +def permutations_ii(nums) + res = [] + backtrack([], nums, Array.new(nums.length, false), res) + res +end + +### Driver Code ### +if __FILE__ == $0 + nums = [1, 2, 2] + + res = permutations_ii(nums) + + puts "输入数组 nums = #{nums}" + puts "所有排列 res = #{res}" +end diff --git a/codes/ruby/chapter_backtracking/preorder_traversal_i_compact.rb b/codes/ruby/chapter_backtracking/preorder_traversal_i_compact.rb new file mode 100644 index 000000000..5745e9cf5 --- /dev/null +++ b/codes/ruby/chapter_backtracking/preorder_traversal_i_compact.rb @@ -0,0 +1,33 @@ +=begin +File: preorder_traversal_i_compact.rb +Created Time: 2024-05-22 +Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) +=end + +require_relative '../utils/tree_node' +require_relative '../utils/print_util' + +### 前序遍历:例题一 ### +def pre_order(root) + return unless root + + # 记录解 + $res << root if root.val == 7 + + pre_order(root.left) + pre_order(root.right) +end + +### Driver Code ### +if __FILE__ == $0 + root = arr_to_tree([1, 7, 3, 4, 5, 6, 7]) + puts "\n初始化二叉树" + print_tree(root) + + # 前序遍历 + $res = [] + pre_order(root) + + puts "\n输出所有值为 7 的节点" + p $res.map { |node| node.val } +end diff --git a/codes/ruby/chapter_backtracking/preorder_traversal_ii_compact.rb b/codes/ruby/chapter_backtracking/preorder_traversal_ii_compact.rb new file mode 100644 index 000000000..58a9e9073 --- /dev/null +++ b/codes/ruby/chapter_backtracking/preorder_traversal_ii_compact.rb @@ -0,0 +1,41 @@ +=begin +File: preorder_traversal_ii_compact.rb +Created Time: 2024-05-22 +Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) +=end + +require_relative '../utils/tree_node' +require_relative '../utils/print_util' + +### 前序遍历:例题二 ### +def pre_order(root) + return unless root + + # 尝试 + $path << root + + # 记录解 + $res << $path.dup if root.val == 7 + + pre_order(root.left) + pre_order(root.right) + + # 回退 + $path.pop +end + +### Driver Code ### +if __FILE__ == $0 + root = arr_to_tree([1, 7, 3, 4, 5, 6, 7]) + puts "\n初始化二叉树" + print_tree(root) + + # 前序遍历 + $path, $res = [], [] + pre_order(root) + + puts "\n输出所有根节点到节点 7 的路径" + for path in $res + p path.map { |node| node.val } + end +end diff --git a/codes/ruby/chapter_backtracking/preorder_traversal_iii_compact.rb b/codes/ruby/chapter_backtracking/preorder_traversal_iii_compact.rb new file mode 100644 index 000000000..9dcd79545 --- /dev/null +++ b/codes/ruby/chapter_backtracking/preorder_traversal_iii_compact.rb @@ -0,0 +1,42 @@ +=begin +File: preorder_traversal_iii_compact.rb +Created Time: 2024-05-22 +Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) +=end + +require_relative '../utils/tree_node' +require_relative '../utils/print_util' + +### 前序遍历:例题三 ### +def pre_order(root) + # 剪枝 + return if !root || root.val == 3 + + # 尝试 + $path.append(root) + + # 记录解 + $res << $path.dup if root.val == 7 + + pre_order(root.left) + pre_order(root.right) + + # 回退 + $path.pop +end + +### Driver Code ### +if __FILE__ == $0 + root = arr_to_tree([1, 7, 3, 4, 5, 6, 7]) + puts "\n初始化二叉树" + print_tree(root) + + # 前序遍历 + $path, $res = [], [] + pre_order(root) + + puts "\n输出所有根节点到节点 7 的路径,路径中不包含值为 3 的节点" + for path in $res + p path.map { |node| node.val } + end +end diff --git a/codes/ruby/chapter_backtracking/preorder_traversal_iii_template.rb b/codes/ruby/chapter_backtracking/preorder_traversal_iii_template.rb new file mode 100644 index 000000000..7de0a8213 --- /dev/null +++ b/codes/ruby/chapter_backtracking/preorder_traversal_iii_template.rb @@ -0,0 +1,68 @@ +=begin +File: preorder_traversal_iii_template.rb +Created Time: 2024-05-22 +Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) +=end + +require_relative '../utils/tree_node' +require_relative '../utils/print_util' + +### 判断当前状态是否为解 ### +def is_solution?(state) + !state.empty? && state.last.val == 7 +end + +### 记录解 ### +def record_solution(state, res) + res << state.dup +end + +### 判断在当前状态下,该选择是否合法 ### +def is_valid?(state, choice) + choice && choice.val != 3 +end + +### 更新状态 ### +def make_choice(state, choice) + state << choice +end + +### 恢复状态 ### +def undo_choice(state, choice) + state.pop +end + +### 回溯算法:例题三 ### +def backtrack(state, choices, res) + # 检查是否为解 + record_solution(state, res) if is_solution?(state) + + # 遍历所有选择 + for choice in choices + # 剪枝:检查选择是否合法 + if is_valid?(state, choice) + # 尝试:做出选择,更新状态 + make_choice(state, choice) + # 进行下一轮选择 + backtrack(state, [choice.left, choice.right], res) + # 回退:撤销选择,恢复到之前的状态 + undo_choice(state, choice) + end + end +end + +### Driver Code ### +if __FILE__ == $0 + root = arr_to_tree([1, 7, 3, 4, 5, 6, 7]) + puts "\n初始化二叉树" + print_tree(root) + + # 回溯算法 + res = [] + backtrack([], [root], res) + + puts "\n输出所有根节点到节点 7 的路径,要求路径中不包含值为 3 的节点" + for path in res + p path.map { |node| node.val } + end +end diff --git a/codes/ruby/chapter_backtracking/subset_sum_i.rb b/codes/ruby/chapter_backtracking/subset_sum_i.rb new file mode 100644 index 000000000..76eac9b6b --- /dev/null +++ b/codes/ruby/chapter_backtracking/subset_sum_i.rb @@ -0,0 +1,47 @@ +=begin +File: subset_sum_i.rb +Created Time: 2024-05-22 +Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) +=end + +### 回溯算法:子集和 I ### +def backtrack(state, target, choices, start, res) + # 子集和等于 target 时,记录解 + if target.zero? + res << state.dup + return + end + # 遍历所有选择 + # 剪枝二:从 start 开始遍历,避免生成重复子集 + for i in start...choices.length + # 剪枝一:若子集和超过 target ,则直接结束循环 + # 这是因为数组已排序,后边元素更大,子集和一定超过 target + break if target - choices[i] < 0 + # 尝试:做出选择,更新 target, start + state << choices[i] + # 进行下一轮选择 + backtrack(state, target - choices[i], choices, i, res) + # 回退:撤销选择,恢复到之前的状态 + state.pop + end +end + +### 求解子集和 I ### +def subset_sum_i(nums, target) + state = [] # 状态(子集) + nums.sort! # 对 nums 进行排序 + start = 0 # 遍历起始点 + res = [] # 结果列表(子集列表) + backtrack(state, target, nums, start, res) + res +end + +### Driver Code ### +if __FILE__ == $0 + nums = [3, 4, 5] + target = 9 + res = subset_sum_i(nums, target) + + puts "输入数组 = #{nums}, target = #{target}" + puts "所有和等于 #{target} 的子集 res = #{res}" +end diff --git a/codes/ruby/chapter_backtracking/subset_sum_i_naive.rb b/codes/ruby/chapter_backtracking/subset_sum_i_naive.rb new file mode 100644 index 000000000..276cb5394 --- /dev/null +++ b/codes/ruby/chapter_backtracking/subset_sum_i_naive.rb @@ -0,0 +1,46 @@ +=begin +File: subset_sum_i_naive.rb +Created Time: 2024-05-22 +Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) +=end + +### 回溯算法:子集和 I ### +def backtrack(state, target, total, choices, res) + # 子集和等于 target 时,记录解 + if total == target + res << state.dup + return + end + + # 遍历所有选择 + for i in 0...choices.length + # 剪枝:若子集和超过 target ,则跳过该选择 + next if total + choices[i] > target + # 尝试:做出选择,更新元素和 total + state << choices[i] + # 进行下一轮选择 + backtrack(state, target, total + choices[i], choices, res) + # 回退:撤销选择,恢复到之前的状态 + state.pop + end +end + +### 求解子集和 I(包含重复子集)### +def subset_sum_i_naive(nums, target) + state = [] # 状态(子集) + total = 0 # 子集和 + res = [] # 结果列表(子集列表) + backtrack(state, target, total, nums, res) + res +end + +### Driver Code ### +if __FILE__ == $0 + nums = [3, 4, 5] + target = 9 + res = subset_sum_i_naive(nums, target) + + puts "输入数组 nums = #{nums}, target = #{target}" + puts "所有和等于 #{target} 的子集 res = #{res}" + puts "请注意,该方法输出的结果包含重复集合" +end diff --git a/codes/ruby/chapter_backtracking/subset_sum_ii.rb b/codes/ruby/chapter_backtracking/subset_sum_ii.rb new file mode 100644 index 000000000..a25861bee --- /dev/null +++ b/codes/ruby/chapter_backtracking/subset_sum_ii.rb @@ -0,0 +1,51 @@ +=begin +File: subset_sum_ii.rb +Created Time: 2024-05-22 +Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) +=end + +### 回溯算法:子集和 II ### +def backtrack(state, target, choices, start, res) + # 子集和等于 target 时,记录解 + if target.zero? + res << state.dup + return + end + + # 遍历所有选择 + # 剪枝二:从 start 开始遍历,避免生成重复子集 + # 剪枝三:从 start 开始遍历,避免重复选择同一元素 + for i in start...choices.length + # 剪枝一:若子集和超过 target ,则直接结束循环 + # 这是因为数组已排序,后边元素更大,子集和一定超过 target + break if target - choices[i] < 0 + # 剪枝四:如果该元素与左边元素相等,说明该搜索分支重复,直接跳过 + next if i > start && choices[i] == choices[i - 1] + # 尝试:做出选择,更新 target, start + state << choices[i] + # 进行下一轮选择 + backtrack(state, target - choices[i], choices, i + 1, res) + # 回退:撤销选择,恢复到之前的状态 + state.pop + end +end + +### 求解子集和 II ### +def subset_sum_ii(nums, target) + state = [] # 状态(子集) + nums.sort! # 对 nums 进行排序 + start = 0 # 遍历起始点 + res = [] # 结果列表(子集列表) + backtrack(state, target, nums, start, res) + res +end + +### Driver Code ### +if __FILE__ == $0 + nums = [4, 4, 5] + target = 9 + res = subset_sum_ii(nums, target) + + puts "输入数组 nums = #{nums}, target = #{target}" + puts "所有和等于 #{target} 的子集 res = #{res}" +end diff --git a/docs/chapter_backtracking/backtracking_algorithm.md b/docs/chapter_backtracking/backtracking_algorithm.md index 874f8d64c..dcdebd677 100644 --- a/docs/chapter_backtracking/backtracking_algorithm.md +++ b/docs/chapter_backtracking/backtracking_algorithm.md @@ -406,7 +406,27 @@ === "Ruby" ```ruby title="" + ### 回溯算法框架 ### + def backtrack(state, choices, res) + # 判断是否为解 + if is_solution?(state) + # 记录解 + record_solution(state, res) + return + end + # 遍历所有选择 + for choice in choices + # 剪枝:判断选择是否合法 + if is_valid?(state, choice) + # 尝试:做出选择,更新状态 + make_choice(state, choice) + backtrack(state, choices, res) + # 回退:撤销选择,恢复到之前的状态 + undo_choice(state, choice) + end + end + end ``` === "Zig"